Skip to main content

celox_runtime/
simulation.rs

1use bit_set::BitSet;
2use celox_design::DomainKind;
3use fxhash::FxHashMap;
4
5use crate::{
6    AbsoluteAddr, SignalRef, SimulatorErrorCode,
7    backend::{EventHandle, SimBackend},
8    scheduler::{ClockDef, Scheduler, SimEvent},
9};
10
11/// Backend execution hooks needed by the timed simulation engine.
12///
13/// The facade implements this contract to retain policy such as runtime-event
14/// decoration and waveform capture outside the backend-independent scheduler.
15pub trait SimulationExecutor {
16    type Backend: SimBackend;
17
18    fn backend(&self) -> &Self::Backend;
19    fn backend_mut(&mut self) -> &mut Self::Backend;
20    fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode>;
21    fn eval_apply_ff_at(
22        &mut self,
23        event: <Self::Backend as SimBackend>::Event,
24    ) -> Result<(), SimulatorErrorCode>;
25    fn eval_only_ff_at(
26        &mut self,
27        event: <Self::Backend as SimBackend>::Event,
28    ) -> Result<(), SimulatorErrorCode>;
29    fn apply_ff_at(
30        &mut self,
31        event: <Self::Backend as SimBackend>::Event,
32    ) -> Result<(), SimulatorErrorCode>;
33
34    /// Snapshot external-component inputs immediately before an event domain
35    /// evaluates its sequential logic.
36    fn stage_external_event(
37        &mut self,
38        _event: <Self::Backend as SimBackend>::Event,
39        _timestamp: u64,
40    ) -> Result<(), SimulatorErrorCode> {
41        Ok(())
42    }
43
44    /// Fire external-component hooks after the event domain commits and
45    /// before the following combinational settle.
46    fn fire_external_event(
47        &mut self,
48        _event: <Self::Backend as SimBackend>::Event,
49        _timestamp: u64,
50    ) -> Result<(), SimulatorErrorCode> {
51        Ok(())
52    }
53
54    /// Called after the state for a simulation timestamp has stabilized.
55    fn finish_timed_step(&mut self, _timestamp: u64) {}
56}
57
58/// Runtime metadata for one event domain.
59pub struct EventInfo<B: SimBackend> {
60    pub canonical_id: usize,
61    pub is_cascaded: bool,
62    pub eval_ff_event: Option<B::Event>,
63    pub eval_only_event: Option<B::Event>,
64    pub apply_event: Option<B::Event>,
65}
66
67impl<B: SimBackend> Clone for EventInfo<B> {
68    fn clone(&self) -> Self {
69        *self
70    }
71}
72
73impl<B: SimBackend> Copy for EventInfo<B> {}
74
75impl<B: SimBackend> std::fmt::Debug for EventInfo<B> {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.debug_struct("EventInfo")
78            .field("canonical_id", &self.canonical_id)
79            .field("is_cascaded", &self.is_cascaded)
80            .field("eval_ff_event", &self.eval_ff_event)
81            .field("eval_only_event", &self.eval_only_event)
82            .field("apply_event", &self.apply_event)
83            .finish()
84    }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88struct PeriodicEventKey {
89    time: u64,
90    event_id: usize,
91    event_addr: AbsoluteAddr,
92    signal: SignalRef,
93    next_val: u8,
94}
95
96impl PeriodicEventKey {
97    fn from_event<B: SimBackend>(event: &SimEvent<B>) -> Self {
98        Self {
99            time: event.time,
100            event_id: event.event_ref.id(),
101            event_addr: event.event_ref.addr(),
102            signal: event.signal,
103            next_val: event.next_val,
104        }
105    }
106}
107
108/// Backend-independent state and execution rules for timed simulation.
109pub struct SimulationState<B: SimBackend> {
110    scheduler: Scheduler<B>,
111    periodic_events: FxHashMap<PeriodicEventKey, usize>,
112    last_clock_values: BitSet,
113    topo_signals: Vec<(SignalRef, usize, usize)>,
114    domain_kinds: Vec<Option<DomainKind>>,
115    event_info: Vec<EventInfo<B>>,
116    signal_to_id: FxHashMap<SignalRef, usize>,
117}
118
119impl<B: SimBackend> SimulationState<B> {
120    /// Rebase edge detection after state was advanced outside this scheduler.
121    pub fn synchronize_event_values(&mut self, backend: &B) {
122        self.last_clock_values.make_empty();
123        for (signal, id, _) in &self.topo_signals {
124            if *id == usize::MAX {
125                continue;
126            }
127            let value: u8 = backend.get_as(*signal);
128            if value != 0 {
129                self.last_clock_values.insert(*id);
130            }
131        }
132    }
133
134    fn replace_triggers_with_stable_edges(&self, backend: &mut B) {
135        backend.clear_triggered_bits();
136        for (signal, id, _) in &self.topo_signals {
137            if *id == usize::MAX {
138                continue;
139            }
140            let was_nonzero = self.last_clock_values.contains(*id);
141            let value: u8 = backend.get_as(*signal);
142            let is_nonzero = value != 0;
143            let triggered = match self.domain_kinds[*id] {
144                Some(DomainKind::ClockPosedge | DomainKind::ResetAsyncHigh) => {
145                    !was_nonzero && is_nonzero
146                }
147                Some(DomainKind::ClockNegedge | DomainKind::ResetAsyncLow) => {
148                    was_nonzero && !is_nonzero
149                }
150                _ => was_nonzero != is_nonzero,
151            };
152            if triggered {
153                backend.mark_triggered_bit(*id);
154            }
155        }
156    }
157
158    pub fn new(
159        backend: &B,
160        topo_signals: Vec<(SignalRef, usize, usize)>,
161        domain_kinds: Vec<Option<DomainKind>>,
162        event_info: Vec<EventInfo<B>>,
163    ) -> Self {
164        let mut last_clock_values = BitSet::with_capacity(backend.num_events());
165        let mut signal_to_id = FxHashMap::default();
166        for (signal, id, _) in topo_signals.iter().copied() {
167            if id == usize::MAX {
168                continue;
169            }
170            signal_to_id.insert(signal, id);
171            let value: u8 = backend.get_as(signal);
172            if value != 0 {
173                last_clock_values.insert(id);
174            }
175        }
176
177        Self {
178            scheduler: Scheduler::new(),
179            periodic_events: FxHashMap::default(),
180            last_clock_values,
181            topo_signals,
182            domain_kinds,
183            event_info,
184            signal_to_id,
185        }
186    }
187
188    pub fn add_clock(
189        &mut self,
190        event: B::Event,
191        signal: SignalRef,
192        period: u64,
193        initial_delay: u64,
194    ) {
195        let event_id = event.id();
196        if event_id >= self.scheduler.clocks.len() {
197            self.scheduler.clocks.resize(event_id + 1, None);
198        }
199        self.scheduler.clocks[event_id] = Some(ClockDef { period });
200        self.push_periodic_event(SimEvent {
201            time: initial_delay,
202            event_ref: event,
203            signal,
204            next_val: 1,
205        });
206    }
207
208    pub fn schedule(&mut self, event: B::Event, signal: SignalRef, time: u64, value: u8) {
209        self.scheduler.push(SimEvent {
210            time,
211            event_ref: event,
212            signal,
213            next_val: value,
214        });
215    }
216
217    fn push_periodic_event(&mut self, event: SimEvent<B>) {
218        *self
219            .periodic_events
220            .entry(PeriodicEventKey::from_event(&event))
221            .or_default() += 1;
222        self.scheduler.push(event);
223    }
224
225    pub fn step<E>(&mut self, executor: &mut E) -> Result<Option<u64>, SimulatorErrorCode>
226    where
227        E: SimulationExecutor<Backend = B>,
228    {
229        let (current_time, events_to_process) = match self.scheduler.pop_all_at_next_time() {
230            Some(events) => events,
231            None => return Ok(None),
232        };
233        self.scheduler.time = current_time;
234
235        // Keep periodic provenance private so the public SimEvent shape stays
236        // stable. Consume matching sidecar counts before fallible work so an
237        // execution error cannot leave a stale periodic marker behind.
238        let mut periodic_events_to_process: Vec<SimEvent<B>> = Vec::new();
239        for event in &events_to_process {
240            let key = PeriodicEventKey::from_event(event);
241            let mut remove_key = false;
242            if let Some(count) = self.periodic_events.get_mut(&key) {
243                periodic_events_to_process.push(SimEvent {
244                    time: event.time,
245                    event_ref: event.event_ref,
246                    signal: event.signal,
247                    next_val: event.next_val,
248                });
249                *count -= 1;
250                remove_key = *count == 0;
251            }
252            if remove_key {
253                self.periodic_events.remove(&key);
254            }
255        }
256        debug_assert!(
257            self.periodic_events
258                .keys()
259                .all(|event| event.time > current_time),
260            "periodic event sidecar fell behind the scheduler"
261        );
262
263        let num_events = executor.backend().num_events();
264        for event in &events_to_process {
265            executor.backend_mut().set(event.signal, event.next_val);
266        }
267
268        let mut triggered_domains = BitSet::with_capacity(num_events);
269        let mut discovered_in_this_step = BitSet::with_capacity(num_events);
270        let mut scheduled_trigger_ids = BitSet::with_capacity(num_events);
271        let mut has_scheduled_event_signal = false;
272        executor.backend_mut().clear_triggered_bits();
273
274        for event in &events_to_process {
275            if let Some(&id) = self.signal_to_id.get(&event.signal) {
276                has_scheduled_event_signal = true;
277                let was_nonzero = self.last_clock_values.contains(id);
278                let is_nonzero = event.next_val != 0;
279                let triggered = match self.domain_kinds[id] {
280                    Some(DomainKind::ClockPosedge | DomainKind::ResetAsyncHigh) => {
281                        !was_nonzero && is_nonzero
282                    }
283                    Some(DomainKind::ClockNegedge | DomainKind::ResetAsyncLow) => {
284                        was_nonzero && !is_nonzero
285                    }
286                    _ => !was_nonzero && is_nonzero,
287                };
288                if triggered {
289                    scheduled_trigger_ids.insert(id);
290                    executor.backend_mut().mark_triggered_bit(id);
291                }
292            }
293        }
294
295        executor.eval_comb()?;
296        if has_scheduled_event_signal {
297            // Combinational settling before an active scheduled source domain
298            // commits may expose transient derived-clock edges. In that case,
299            // keep only the source event and rediscover stable edges after the
300            // commit. If the source edge is inactive, preserve derived edges
301            // while filtering out the scheduled signal's own transition.
302            if scheduled_trigger_ids.is_empty() {
303                self.replace_triggers_with_stable_edges(executor.backend_mut());
304            } else {
305                executor.backend_mut().clear_triggered_bits();
306                for id in scheduled_trigger_ids.iter() {
307                    executor.backend_mut().mark_triggered_bit(id);
308                }
309            }
310        }
311
312        let mut comb_already_done = false;
313        loop {
314            let mut any_new_outer_loop_trigger = false;
315            let mut newly_triggered = Vec::new();
316
317            loop {
318                let mut any_new_sequential_trigger = false;
319                let marked_bits = executor.backend().get_triggered_bits();
320                executor.backend_mut().clear_triggered_bits();
321
322                let mut can_use_eval_apply =
323                    triggered_domains.is_empty() && marked_bits.count() == 1;
324                if can_use_eval_apply {
325                    let single_id = marked_bits.iter().next().expect("one marked trigger");
326                    let info = self.event_info[single_id];
327                    can_use_eval_apply = !info.is_cascaded;
328                    if can_use_eval_apply {
329                        if let Some(event) = info.eval_ff_event {
330                            discovered_in_this_step.insert(single_id);
331                            triggered_domains.insert(info.canonical_id);
332                            any_new_outer_loop_trigger = true;
333                            executor.stage_external_event(event, current_time)?;
334                            executor.eval_apply_ff_at(event)?;
335                            executor.fire_external_event(event, current_time)?;
336                            executor.eval_comb()?;
337                            if has_scheduled_event_signal {
338                                self.replace_triggers_with_stable_edges(executor.backend_mut());
339                            }
340                            comb_already_done = true;
341                            break;
342                        }
343                    }
344                }
345
346                for id in marked_bits.iter() {
347                    if discovered_in_this_step.contains(id) {
348                        continue;
349                    }
350                    discovered_in_this_step.insert(id);
351
352                    let info = self.event_info[id];
353                    if triggered_domains.contains(info.canonical_id) {
354                        continue;
355                    }
356                    triggered_domains.insert(info.canonical_id);
357                    any_new_sequential_trigger = true;
358                    newly_triggered.push(info.canonical_id);
359
360                    if let Some(event) = info.eval_only_event {
361                        executor.stage_external_event(
362                            info.eval_ff_event.unwrap_or(event),
363                            current_time,
364                        )?;
365                        executor.eval_only_ff_at(event)?;
366                    } else if let Some(event) = info.eval_ff_event {
367                        executor.stage_external_event(event, current_time)?;
368                        executor.eval_apply_ff_at(event)?;
369                    } else {
370                        unreachable!(
371                            "FF trigger discovered without a corresponding execution unit"
372                        );
373                    }
374                }
375
376                if !any_new_sequential_trigger {
377                    break;
378                }
379            }
380
381            if newly_triggered.is_empty() && !any_new_outer_loop_trigger {
382                break;
383            }
384
385            for id in &newly_triggered {
386                if let Some(event) = self.event_info[*id].apply_event {
387                    executor.apply_ff_at(event)?;
388                }
389            }
390            for id in &newly_triggered {
391                if let Some(event) = self.event_info[*id].eval_ff_event {
392                    executor.fire_external_event(event, current_time)?;
393                }
394            }
395
396            if comb_already_done {
397                comb_already_done = false;
398            } else {
399                executor.eval_comb()?;
400                if has_scheduled_event_signal {
401                    self.replace_triggers_with_stable_edges(executor.backend_mut());
402                }
403            }
404        }
405
406        for (signal, id, _) in &self.topo_signals {
407            if *id == usize::MAX {
408                continue;
409            }
410            let value: u8 = executor.backend().get_as(*signal);
411            if value != 0 {
412                self.last_clock_values.insert(*id);
413            } else {
414                self.last_clock_values.remove(*id);
415            }
416        }
417
418        for event in periodic_events_to_process {
419            let event_id = event.event_ref.id();
420            if let Some(Some(clock)) = self.scheduler.clocks.get(event_id) {
421                self.push_periodic_event(SimEvent {
422                    time: current_time + clock.period / 2,
423                    event_ref: event.event_ref,
424                    signal: event.signal,
425                    next_val: 1 - event.next_val,
426                });
427            }
428        }
429
430        executor.finish_timed_step(current_time);
431        Ok(Some(current_time))
432    }
433
434    pub fn time(&self) -> u64 {
435        self.scheduler.time
436    }
437
438    pub fn set_time(&mut self, time: u64) {
439        self.scheduler.time = time;
440    }
441
442    pub fn next_event_time(&self) -> Option<u64> {
443        self.scheduler.next_event_time()
444    }
445}