Skip to main content

celox_runtime/
simulation.rs

1use bit_set::BitSet;
2use celox_design::DomainKind;
3use fxhash::FxHashMap;
4
5use crate::{
6    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/// Backend-independent state and execution rules for timed simulation.
88pub struct SimulationState<B: SimBackend> {
89    scheduler: Scheduler<B>,
90    last_clock_values: BitSet,
91    topo_signals: Vec<(SignalRef, usize, usize)>,
92    domain_kinds: Vec<Option<DomainKind>>,
93    event_info: Vec<EventInfo<B>>,
94    signal_to_id: FxHashMap<SignalRef, usize>,
95}
96
97impl<B: SimBackend> SimulationState<B> {
98    /// Rebase edge detection after state was advanced outside this scheduler.
99    pub fn synchronize_event_values(&mut self, backend: &B) {
100        self.last_clock_values.make_empty();
101        for (signal, id, _) in &self.topo_signals {
102            if *id == usize::MAX {
103                continue;
104            }
105            let value: u8 = backend.get_as(*signal);
106            if value != 0 {
107                self.last_clock_values.insert(*id);
108            }
109        }
110    }
111
112    fn replace_triggers_with_stable_edges(&self, backend: &mut B) {
113        backend.clear_triggered_bits();
114        for (signal, id, _) in &self.topo_signals {
115            if *id == usize::MAX {
116                continue;
117            }
118            let was_nonzero = self.last_clock_values.contains(*id);
119            let value: u8 = backend.get_as(*signal);
120            let is_nonzero = value != 0;
121            let triggered = match self.domain_kinds[*id] {
122                Some(DomainKind::ClockPosedge | DomainKind::ResetAsyncHigh) => {
123                    !was_nonzero && is_nonzero
124                }
125                Some(DomainKind::ClockNegedge | DomainKind::ResetAsyncLow) => {
126                    was_nonzero && !is_nonzero
127                }
128                _ => was_nonzero != is_nonzero,
129            };
130            if triggered {
131                backend.mark_triggered_bit(*id);
132            }
133        }
134    }
135
136    pub fn new(
137        backend: &B,
138        topo_signals: Vec<(SignalRef, usize, usize)>,
139        domain_kinds: Vec<Option<DomainKind>>,
140        event_info: Vec<EventInfo<B>>,
141    ) -> Self {
142        let mut last_clock_values = BitSet::with_capacity(backend.num_events());
143        let mut signal_to_id = FxHashMap::default();
144        for (signal, id, _) in topo_signals.iter().copied() {
145            if id == usize::MAX {
146                continue;
147            }
148            signal_to_id.insert(signal, id);
149            let value: u8 = backend.get_as(signal);
150            if value != 0 {
151                last_clock_values.insert(id);
152            }
153        }
154
155        Self {
156            scheduler: Scheduler::new(),
157            last_clock_values,
158            topo_signals,
159            domain_kinds,
160            event_info,
161            signal_to_id,
162        }
163    }
164
165    pub fn add_clock(
166        &mut self,
167        event: B::Event,
168        signal: SignalRef,
169        period: u64,
170        initial_delay: u64,
171    ) {
172        let event_id = event.id();
173        if event_id >= self.scheduler.clocks.len() {
174            self.scheduler.clocks.resize(event_id + 1, None);
175        }
176        self.scheduler.clocks[event_id] = Some(ClockDef { period });
177        self.scheduler.push(SimEvent {
178            time: initial_delay,
179            event_ref: event,
180            signal,
181            next_val: 1,
182        });
183    }
184
185    pub fn schedule(&mut self, event: B::Event, signal: SignalRef, time: u64, value: u8) {
186        self.scheduler.push(SimEvent {
187            time,
188            event_ref: event,
189            signal,
190            next_val: value,
191        });
192    }
193
194    pub fn step<E>(&mut self, executor: &mut E) -> Result<Option<u64>, SimulatorErrorCode>
195    where
196        E: SimulationExecutor<Backend = B>,
197    {
198        let (current_time, events_to_process) = match self.scheduler.pop_all_at_next_time() {
199            Some(events) => events,
200            None => return Ok(None),
201        };
202        self.scheduler.time = current_time;
203
204        let num_events = executor.backend().num_events();
205        for event in &events_to_process {
206            executor.backend_mut().set(event.signal, event.next_val);
207        }
208
209        let mut triggered_domains = BitSet::with_capacity(num_events);
210        let mut discovered_in_this_step = BitSet::with_capacity(num_events);
211        let mut scheduled_trigger_ids = BitSet::with_capacity(num_events);
212        let mut has_scheduled_event_signal = false;
213        executor.backend_mut().clear_triggered_bits();
214
215        for event in &events_to_process {
216            if let Some(&id) = self.signal_to_id.get(&event.signal) {
217                has_scheduled_event_signal = true;
218                let was_nonzero = self.last_clock_values.contains(id);
219                let is_nonzero = event.next_val != 0;
220                let triggered = match self.domain_kinds[id] {
221                    Some(DomainKind::ClockPosedge | DomainKind::ResetAsyncHigh) => {
222                        !was_nonzero && is_nonzero
223                    }
224                    Some(DomainKind::ClockNegedge | DomainKind::ResetAsyncLow) => {
225                        was_nonzero && !is_nonzero
226                    }
227                    _ => !was_nonzero && is_nonzero,
228                };
229                if triggered {
230                    scheduled_trigger_ids.insert(id);
231                    executor.backend_mut().mark_triggered_bit(id);
232                }
233            }
234        }
235
236        executor.eval_comb()?;
237        if has_scheduled_event_signal {
238            // Combinational settling before an active scheduled source domain
239            // commits may expose transient derived-clock edges. In that case,
240            // keep only the source event and rediscover stable edges after the
241            // commit. If the source edge is inactive, preserve derived edges
242            // while filtering out the scheduled signal's own transition.
243            if scheduled_trigger_ids.is_empty() {
244                self.replace_triggers_with_stable_edges(executor.backend_mut());
245            } else {
246                executor.backend_mut().clear_triggered_bits();
247                for id in scheduled_trigger_ids.iter() {
248                    executor.backend_mut().mark_triggered_bit(id);
249                }
250            }
251        }
252
253        let mut comb_already_done = false;
254        loop {
255            let mut any_new_outer_loop_trigger = false;
256            let mut newly_triggered = Vec::new();
257
258            loop {
259                let mut any_new_sequential_trigger = false;
260                let marked_bits = executor.backend().get_triggered_bits();
261                executor.backend_mut().clear_triggered_bits();
262
263                let mut can_use_eval_apply =
264                    triggered_domains.is_empty() && marked_bits.count() == 1;
265                if can_use_eval_apply {
266                    let single_id = marked_bits.iter().next().expect("one marked trigger");
267                    let info = self.event_info[single_id];
268                    can_use_eval_apply = !info.is_cascaded;
269                    if can_use_eval_apply {
270                        if let Some(event) = info.eval_ff_event {
271                            discovered_in_this_step.insert(single_id);
272                            triggered_domains.insert(info.canonical_id);
273                            any_new_outer_loop_trigger = true;
274                            executor.stage_external_event(event, current_time)?;
275                            executor.eval_apply_ff_at(event)?;
276                            executor.fire_external_event(event, current_time)?;
277                            executor.eval_comb()?;
278                            if has_scheduled_event_signal {
279                                self.replace_triggers_with_stable_edges(executor.backend_mut());
280                            }
281                            comb_already_done = true;
282                            break;
283                        }
284                    }
285                }
286
287                for id in marked_bits.iter() {
288                    if discovered_in_this_step.contains(id) {
289                        continue;
290                    }
291                    discovered_in_this_step.insert(id);
292
293                    let info = self.event_info[id];
294                    if triggered_domains.contains(info.canonical_id) {
295                        continue;
296                    }
297                    triggered_domains.insert(info.canonical_id);
298                    any_new_sequential_trigger = true;
299                    newly_triggered.push(info.canonical_id);
300
301                    if let Some(event) = info.eval_only_event {
302                        executor.stage_external_event(
303                            info.eval_ff_event.unwrap_or(event),
304                            current_time,
305                        )?;
306                        executor.eval_only_ff_at(event)?;
307                    } else if let Some(event) = info.eval_ff_event {
308                        executor.stage_external_event(event, current_time)?;
309                        executor.eval_apply_ff_at(event)?;
310                    } else {
311                        unreachable!(
312                            "FF trigger discovered without a corresponding execution unit"
313                        );
314                    }
315                }
316
317                if !any_new_sequential_trigger {
318                    break;
319                }
320            }
321
322            if newly_triggered.is_empty() && !any_new_outer_loop_trigger {
323                break;
324            }
325
326            for id in &newly_triggered {
327                if let Some(event) = self.event_info[*id].apply_event {
328                    executor.apply_ff_at(event)?;
329                }
330            }
331            for id in &newly_triggered {
332                if let Some(event) = self.event_info[*id].eval_ff_event {
333                    executor.fire_external_event(event, current_time)?;
334                }
335            }
336
337            if comb_already_done {
338                comb_already_done = false;
339            } else {
340                executor.eval_comb()?;
341                if has_scheduled_event_signal {
342                    self.replace_triggers_with_stable_edges(executor.backend_mut());
343                }
344            }
345        }
346
347        for (signal, id, _) in &self.topo_signals {
348            if *id == usize::MAX {
349                continue;
350            }
351            let value: u8 = executor.backend().get_as(*signal);
352            if value != 0 {
353                self.last_clock_values.insert(*id);
354            } else {
355                self.last_clock_values.remove(*id);
356            }
357        }
358
359        for event in &events_to_process {
360            let event_id = event.event_ref.id();
361            if let Some(Some(clock)) = self.scheduler.clocks.get(event_id) {
362                self.scheduler.push(SimEvent {
363                    time: current_time + clock.period / 2,
364                    event_ref: event.event_ref,
365                    signal: event.signal,
366                    next_val: 1 - event.next_val,
367                });
368            }
369        }
370
371        executor.finish_timed_step(current_time);
372        Ok(Some(current_time))
373    }
374
375    pub fn time(&self) -> u64 {
376        self.scheduler.time
377    }
378
379    pub fn set_time(&mut self, time: u64) {
380        self.scheduler.time = time;
381    }
382
383    pub fn next_event_time(&self) -> Option<u64> {
384        self.scheduler.next_event_time()
385    }
386}