Skip to main content

celox_runtime/
simulation.rs

1use std::{cmp::Reverse, collections::BinaryHeap};
2
3use bit_set::BitSet;
4use celox_design::{DomainKind, ProcessStatus};
5use fxhash::FxHashMap;
6
7use crate::{
8    AbsoluteAddr, SignalRef, SimulatorErrorCode,
9    backend::{EventHandle, SimBackend},
10    scheduler::{ClockDef, Scheduler, SimEvent},
11};
12
13/// Backend execution hooks needed by the timed simulation engine.
14///
15/// The facade implements this contract to retain policy such as runtime-event
16/// decoration and waveform capture outside the backend-independent scheduler.
17pub trait SimulationExecutor {
18    type Backend: SimBackend;
19
20    fn backend(&self) -> &Self::Backend;
21    fn backend_mut(&mut self) -> &mut Self::Backend;
22    fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode>;
23    fn eval_apply_ff_at(
24        &mut self,
25        event: <Self::Backend as SimBackend>::Event,
26    ) -> Result<(), SimulatorErrorCode>;
27    fn eval_only_ff_at(
28        &mut self,
29        event: <Self::Backend as SimBackend>::Event,
30    ) -> Result<(), SimulatorErrorCode>;
31    fn apply_ff_at(
32        &mut self,
33        event: <Self::Backend as SimBackend>::Event,
34    ) -> Result<(), SimulatorErrorCode>;
35    fn run_process(&mut self, index: usize) -> Result<(), SimulatorErrorCode> {
36        self.backend_mut().run_process(index)
37    }
38
39    /// Snapshot external-component inputs immediately before an event domain
40    /// evaluates its sequential logic.
41    fn stage_external_event(
42        &mut self,
43        _event: <Self::Backend as SimBackend>::Event,
44        _timestamp: u64,
45    ) -> Result<(), SimulatorErrorCode> {
46        Ok(())
47    }
48
49    /// Fire external-component hooks after the event domain commits and
50    /// before the following combinational settle.
51    fn fire_external_event(
52        &mut self,
53        _event: <Self::Backend as SimBackend>::Event,
54        _timestamp: u64,
55    ) -> Result<(), SimulatorErrorCode> {
56        Ok(())
57    }
58
59    /// Called after the state for a simulation timestamp has stabilized.
60    fn finish_timed_step(&mut self, _timestamp: u64) {}
61}
62
63/// Control slots of one process kernel, resolved to signals of the backend.
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub struct ProcessRefs {
66    pub status: SignalRef,
67    pub delay: SignalRef,
68}
69
70/// A process waiting for simulation time to reach `time`. Ordered so that a
71/// max-heap pops the earliest time first and, within one time, the process
72/// that was declared first.
73type ProcessWakeup = Reverse<(u64, usize)>;
74
75/// Runtime metadata for one event domain.
76pub struct EventInfo<B: SimBackend> {
77    pub canonical_id: usize,
78    pub is_cascaded: bool,
79    pub eval_ff_event: Option<B::Event>,
80    pub eval_only_event: Option<B::Event>,
81    pub apply_event: Option<B::Event>,
82}
83
84impl<B: SimBackend> Clone for EventInfo<B> {
85    fn clone(&self) -> Self {
86        *self
87    }
88}
89
90impl<B: SimBackend> Copy for EventInfo<B> {}
91
92impl<B: SimBackend> std::fmt::Debug for EventInfo<B> {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        f.debug_struct("EventInfo")
95            .field("canonical_id", &self.canonical_id)
96            .field("is_cascaded", &self.is_cascaded)
97            .field("eval_ff_event", &self.eval_ff_event)
98            .field("eval_only_event", &self.eval_only_event)
99            .field("apply_event", &self.apply_event)
100            .finish()
101    }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105struct PeriodicEventKey {
106    time: u64,
107    event_id: usize,
108    event_addr: AbsoluteAddr,
109    signal: SignalRef,
110    next_val: u8,
111}
112
113impl PeriodicEventKey {
114    fn from_event<B: SimBackend>(event: &SimEvent<B>) -> Self {
115        Self {
116            time: event.time,
117            event_id: event.event_ref.id(),
118            event_addr: event.event_ref.addr(),
119            signal: event.signal,
120            next_val: event.next_val,
121        }
122    }
123}
124
125/// Backend-independent state and execution rules for timed simulation.
126pub struct SimulationState<B: SimBackend> {
127    scheduler: Scheduler<B>,
128    periodic_events: FxHashMap<PeriodicEventKey, usize>,
129    last_clock_values: BitSet,
130    topo_signals: Vec<(SignalRef, usize, usize)>,
131    domain_kinds: Vec<Option<DomainKind>>,
132    event_info: Vec<EventInfo<B>>,
133    signal_to_id: FxHashMap<SignalRef, usize>,
134    signal_index_by_id: Vec<usize>,
135    processes: Vec<ProcessRefs>,
136    process_wakeups: BinaryHeap<ProcessWakeup>,
137    /// A process requested the end of the simulation.
138    finished: bool,
139}
140
141impl<B: SimBackend> SimulationState<B> {
142    /// Rebase edge detection after state was advanced outside this scheduler.
143    pub fn synchronize_event_values(&mut self, backend: &B) {
144        self.last_clock_values.make_empty();
145        for (signal, id, _) in &self.topo_signals {
146            if *id == usize::MAX {
147                continue;
148            }
149            let value: u8 = backend.get_as(*signal);
150            if value != 0 {
151                self.last_clock_values.insert(*id);
152            }
153        }
154    }
155
156    fn stable_edge_triggered(&self, backend: &B, id: usize) -> bool {
157        let Some((signal, _, _)) = self.topo_signals.get(self.signal_index_by_id[id]) else {
158            return true;
159        };
160        let was_nonzero = self.last_clock_values.contains(id);
161        let is_nonzero = backend.get_as::<u8>(*signal) != 0;
162        match self.domain_kinds[id] {
163            Some(DomainKind::ClockPosedge | DomainKind::ResetAsyncHigh) => {
164                !was_nonzero && is_nonzero
165            }
166            Some(DomainKind::ClockNegedge | DomainKind::ResetAsyncLow) => {
167                was_nonzero && !is_nonzero
168            }
169            _ => was_nonzero != is_nonzero,
170        }
171    }
172
173    fn replace_triggers_with_stable_edges(&self, backend: &mut B) {
174        backend.clear_triggered_bits();
175        for (signal, id, _) in &self.topo_signals {
176            if *id == usize::MAX {
177                continue;
178            }
179            let was_nonzero = self.last_clock_values.contains(*id);
180            let value: u8 = backend.get_as(*signal);
181            let is_nonzero = value != 0;
182            let triggered = match self.domain_kinds[*id] {
183                Some(DomainKind::ClockPosedge | DomainKind::ResetAsyncHigh) => {
184                    !was_nonzero && is_nonzero
185                }
186                Some(DomainKind::ClockNegedge | DomainKind::ResetAsyncLow) => {
187                    was_nonzero && !is_nonzero
188                }
189                _ => was_nonzero != is_nonzero,
190            };
191            if triggered {
192                backend.mark_triggered_bit(*id);
193            }
194        }
195    }
196
197    /// Every process in `processes` starts at time zero, in list order.
198    pub fn new(
199        backend: &B,
200        topo_signals: Vec<(SignalRef, usize, usize)>,
201        domain_kinds: Vec<Option<DomainKind>>,
202        event_info: Vec<EventInfo<B>>,
203        processes: Vec<ProcessRefs>,
204    ) -> Self {
205        let mut last_clock_values = BitSet::with_capacity(backend.num_events());
206        let mut signal_to_id = FxHashMap::default();
207        let mut signal_index_by_id = vec![usize::MAX; backend.num_events()];
208        for (index, (signal, id, _)) in topo_signals.iter().copied().enumerate() {
209            if id == usize::MAX {
210                continue;
211            }
212            signal_to_id.insert(signal, id);
213            signal_index_by_id[id] = index;
214            let value: u8 = backend.get_as(signal);
215            if value != 0 {
216                last_clock_values.insert(id);
217            }
218        }
219
220        Self {
221            scheduler: Scheduler::new(),
222            periodic_events: FxHashMap::default(),
223            last_clock_values,
224            topo_signals,
225            domain_kinds,
226            event_info,
227            signal_to_id,
228            signal_index_by_id,
229            process_wakeups: (0..processes.len())
230                .map(|process| Reverse((0, process)))
231                .collect(),
232            processes,
233            finished: false,
234        }
235    }
236
237    pub fn add_clock(
238        &mut self,
239        event: B::Event,
240        signal: SignalRef,
241        period: u64,
242        initial_delay: u64,
243    ) {
244        let event_id = event.id();
245        if event_id >= self.scheduler.clocks.len() {
246            self.scheduler.clocks.resize(event_id + 1, None);
247        }
248        self.scheduler.clocks[event_id] = Some(ClockDef {
249            period: period.max(2),
250        });
251        self.push_periodic_event(SimEvent {
252            time: initial_delay,
253            event_ref: event,
254            signal,
255            next_val: 1,
256        });
257    }
258
259    pub fn schedule(&mut self, event: B::Event, signal: SignalRef, time: u64, value: u8) {
260        self.scheduler.push(SimEvent {
261            time,
262            event_ref: event,
263            signal,
264            next_val: value,
265        });
266    }
267
268    fn push_periodic_event(&mut self, event: SimEvent<B>) {
269        *self
270            .periodic_events
271            .entry(PeriodicEventKey::from_event(&event))
272            .or_default() += 1;
273        self.scheduler.push(event);
274    }
275
276    pub fn step<E>(&mut self, executor: &mut E) -> Result<Option<u64>, SimulatorErrorCode>
277    where
278        E: SimulationExecutor<Backend = B>,
279    {
280        let Some(current_time) = self.next_event_time() else {
281            return Ok(None);
282        };
283        let events_to_process = if self.scheduler.next_event_time() == Some(current_time) {
284            self.scheduler
285                .pop_all_at_next_time()
286                .map(|(_, events)| events)
287                .unwrap_or_default()
288        } else {
289            Vec::new()
290        };
291        let ready = self.take_ready_processes(current_time);
292        self.step_round(executor, current_time, events_to_process, ready)?;
293        // A process that waited for zero time resumes in a later round of
294        // this time, after the previous round's edges have been handled.
295        while !self.finished {
296            let ready = self.take_ready_processes(current_time);
297            if ready.is_empty() {
298                break;
299            }
300            self.step_round(executor, current_time, Vec::new(), ready)?;
301        }
302        executor.finish_timed_step(current_time);
303        Ok(Some(current_time))
304    }
305
306    /// Remove the processes waiting for `time`, in declaration order.
307    fn take_ready_processes(&mut self, time: u64) -> Vec<usize> {
308        let mut ready = Vec::new();
309        while let Some(&Reverse((wakeup, process))) = self.process_wakeups.peek() {
310            if wakeup != time {
311                break;
312            }
313            self.process_wakeups.pop();
314            ready.push(process);
315        }
316        ready
317    }
318
319    /// Run `ready` processes in order until each suspends or ends. A process
320    /// that waits for zero time is queued for the next round of this time.
321    fn run_processes<E>(
322        &mut self,
323        executor: &mut E,
324        current_time: u64,
325        ready: Vec<usize>,
326    ) -> Result<(), SimulatorErrorCode>
327    where
328        E: SimulationExecutor<Backend = B>,
329    {
330        for process in ready {
331            executor.run_process(process)?;
332            let refs = self.processes[process];
333            let status: u8 = executor.backend().get_as(refs.status);
334            match ProcessStatus::from_code(status) {
335                Some(ProcessStatus::Delay) => {
336                    let delay: u64 = executor.backend().get_as(refs.delay);
337                    let time = current_time.checked_add(delay).ok_or_else(|| {
338                        SimulatorErrorCode::Runtime {
339                            message: format!("process {process} delay overflows simulation time"),
340                            signals: Vec::new(),
341                        }
342                    })?;
343                    self.process_wakeups.push(Reverse((time, process)));
344                }
345                Some(ProcessStatus::Done) => {}
346                Some(ProcessStatus::Finish) => {
347                    self.finished = true;
348                    break;
349                }
350                None => return Err(SimulatorErrorCode::InternalError),
351            }
352        }
353        Ok(())
354    }
355
356    /// Whether a process requested the end of the simulation.
357    pub fn is_finished(&self) -> bool {
358        self.finished
359    }
360
361    /// Settle externally driven state against the previous edge baseline even
362    /// when no clock/reset signal is explicitly scheduled at this timestamp.
363    pub fn settle_at<E>(
364        &mut self,
365        executor: &mut E,
366        time: u64,
367    ) -> Result<Option<u64>, SimulatorErrorCode>
368    where
369        E: SimulationExecutor<Backend = B>,
370    {
371        self.step_round(executor, time, Vec::new(), Vec::new())?;
372        executor.finish_timed_step(time);
373        Ok(Some(time))
374    }
375
376    /// Apply `events_to_process`, run `ready_processes`, and settle the
377    /// edges they cause.
378    fn step_round<E>(
379        &mut self,
380        executor: &mut E,
381        current_time: u64,
382        events_to_process: Vec<SimEvent<B>>,
383        ready_processes: Vec<usize>,
384    ) -> Result<(), SimulatorErrorCode>
385    where
386        E: SimulationExecutor<Backend = B>,
387    {
388        self.scheduler.time = current_time;
389
390        // Keep periodic provenance private so the public SimEvent shape stays
391        // stable. Consume matching sidecar counts before fallible work so an
392        // execution error cannot leave a stale periodic marker behind.
393        let mut periodic_events_to_process: Vec<SimEvent<B>> = Vec::new();
394        for event in &events_to_process {
395            let key = PeriodicEventKey::from_event(event);
396            let mut remove_key = false;
397            if let Some(count) = self.periodic_events.get_mut(&key) {
398                periodic_events_to_process.push(SimEvent {
399                    time: event.time,
400                    event_ref: event.event_ref,
401                    signal: event.signal,
402                    next_val: event.next_val,
403                });
404                *count -= 1;
405                remove_key = *count == 0;
406            }
407            if remove_key {
408                self.periodic_events.remove(&key);
409            }
410        }
411        debug_assert!(
412            self.periodic_events
413                .keys()
414                .all(|event| event.time > current_time),
415            "periodic event sidecar fell behind the scheduler"
416        );
417
418        let num_events = executor.backend().num_events();
419        for event in &events_to_process {
420            executor.backend_mut().set(event.signal, event.next_val);
421        }
422
423        // Processes run after this time's scheduled values are applied and
424        // see the state settled at the previous time. The event signals they
425        // change are edges of this time, like scheduled events.
426        let mut process_driven = Vec::new();
427        if !ready_processes.is_empty() {
428            let before: Vec<u8> = self
429                .topo_signals
430                .iter()
431                .map(|(signal, _, _)| executor.backend().get_as(*signal))
432                .collect();
433            self.run_processes(executor, current_time, ready_processes)?;
434            for ((signal, id, _), before) in self.topo_signals.iter().zip(before) {
435                if *id == usize::MAX {
436                    continue;
437                }
438                let value: u8 = executor.backend().get_as(*signal);
439                if value != before {
440                    process_driven.push((*id, value != 0));
441                }
442            }
443        }
444
445        let mut triggered_domains = BitSet::with_capacity(num_events);
446        let mut discovered_in_this_step = BitSet::with_capacity(num_events);
447        let mut scheduled_trigger_ids = BitSet::with_capacity(num_events);
448        // An external drive may already have been combinationally evaluated
449        // by a testbench read. Compare against our saved baseline even if that
450        // evaluation's transient trigger bits have since been cleared.
451        let mut track_stable_edges = events_to_process.is_empty();
452        executor.backend_mut().clear_triggered_bits();
453
454        let scheduled = events_to_process
455            .iter()
456            .filter_map(|event| {
457                self.signal_to_id
458                    .get(&event.signal)
459                    .map(|&id| (id, event.next_val != 0))
460            })
461            .chain(process_driven);
462        for (id, is_nonzero) in scheduled {
463            track_stable_edges = true;
464            let was_nonzero = self.last_clock_values.contains(id);
465            let triggered = match self.domain_kinds[id] {
466                Some(DomainKind::ClockPosedge | DomainKind::ResetAsyncHigh) => {
467                    !was_nonzero && is_nonzero
468                }
469                Some(DomainKind::ClockNegedge | DomainKind::ResetAsyncLow) => {
470                    was_nonzero && !is_nonzero
471                }
472                _ => !was_nonzero && is_nonzero,
473            };
474            if triggered {
475                scheduled_trigger_ids.insert(id);
476                executor.backend_mut().mark_triggered_bit(id);
477            }
478        }
479
480        executor.eval_comb()?;
481        if track_stable_edges {
482            // Combinational settling before an active scheduled source domain
483            // commits may expose transient derived-clock edges. In that case,
484            // keep only the source event and rediscover stable edges after the
485            // commit. If the source edge is inactive, preserve derived edges
486            // while filtering out the scheduled signal's own transition.
487            if scheduled_trigger_ids.is_empty() {
488                self.replace_triggers_with_stable_edges(executor.backend_mut());
489            } else {
490                executor.backend_mut().clear_triggered_bits();
491                for id in scheduled_trigger_ids.iter() {
492                    executor.backend_mut().mark_triggered_bit(id);
493                }
494            }
495        }
496
497        let mut comb_already_done = false;
498        loop {
499            let mut any_new_outer_loop_trigger = false;
500            let mut newly_triggered = Vec::new();
501
502            loop {
503                let mut any_new_sequential_trigger = false;
504                let marked_bits = executor.backend().get_triggered_bits();
505                executor.backend_mut().clear_triggered_bits();
506
507                let mut can_use_eval_apply =
508                    triggered_domains.is_empty() && marked_bits.count() == 1;
509                if can_use_eval_apply {
510                    let single_id = marked_bits.iter().next().expect("one marked trigger");
511                    let info = self.event_info[single_id];
512                    can_use_eval_apply = !info.is_cascaded;
513                    if can_use_eval_apply {
514                        if let Some(event) = info.eval_ff_event {
515                            discovered_in_this_step.insert(single_id);
516                            triggered_domains.insert(info.canonical_id);
517                            any_new_outer_loop_trigger = true;
518                            executor.stage_external_event(event, current_time)?;
519                            executor.eval_apply_ff_at(event)?;
520                            executor.fire_external_event(event, current_time)?;
521                            executor.eval_comb()?;
522                            if track_stable_edges {
523                                self.replace_triggers_with_stable_edges(executor.backend_mut());
524                            }
525                            comb_already_done = true;
526                            break;
527                        }
528                    }
529                }
530
531                for id in marked_bits.iter() {
532                    if discovered_in_this_step.contains(id) {
533                        continue;
534                    }
535                    let info = self.event_info[id];
536                    if triggered_domains.contains(info.canonical_id) {
537                        continue;
538                    }
539                    // Native trigger bits can be coarse hints raised by a
540                    // working-region write before its commit. A derived clock
541                    // must have the requested stable edge before its FFs run.
542                    if info.is_cascaded && !self.stable_edge_triggered(executor.backend(), id) {
543                        continue;
544                    }
545                    discovered_in_this_step.insert(id);
546                    triggered_domains.insert(info.canonical_id);
547                    any_new_sequential_trigger = true;
548                    newly_triggered.push(info.canonical_id);
549
550                    if let Some(event) = info.eval_only_event {
551                        executor.stage_external_event(
552                            info.eval_ff_event.unwrap_or(event),
553                            current_time,
554                        )?;
555                        executor.eval_only_ff_at(event)?;
556                    } else if let Some(event) = info.eval_ff_event {
557                        executor.stage_external_event(event, current_time)?;
558                        executor.eval_apply_ff_at(event)?;
559                    } else {
560                        unreachable!(
561                            "FF trigger discovered without a corresponding execution unit"
562                        );
563                    }
564                }
565
566                if !any_new_sequential_trigger {
567                    break;
568                }
569            }
570
571            if newly_triggered.is_empty() && !any_new_outer_loop_trigger {
572                break;
573            }
574
575            for id in &newly_triggered {
576                if let Some(event) = self.event_info[*id].apply_event {
577                    executor.apply_ff_at(event)?;
578                }
579            }
580            for id in &newly_triggered {
581                if let Some(event) = self.event_info[*id].eval_ff_event {
582                    executor.fire_external_event(event, current_time)?;
583                }
584            }
585
586            if comb_already_done {
587                comb_already_done = false;
588            } else {
589                executor.eval_comb()?;
590                if track_stable_edges {
591                    self.replace_triggers_with_stable_edges(executor.backend_mut());
592                }
593            }
594        }
595
596        for (signal, id, _) in &self.topo_signals {
597            if *id == usize::MAX {
598                continue;
599            }
600            let value: u8 = executor.backend().get_as(*signal);
601            if value != 0 {
602                self.last_clock_values.insert(*id);
603            } else {
604                self.last_clock_values.remove(*id);
605            }
606        }
607
608        for event in periodic_events_to_process {
609            let event_id = event.event_ref.id();
610            if let Some(Some(clock)) = self.scheduler.clocks.get(event_id) {
611                // Keep the extra odd-period tick in the high phase, matching
612                // the process/testbench clock model.
613                // A clock needs at least one time unit in each phase.
614                let period = clock.period.max(2);
615                let half = (period / 2) + ((period & 1) & u64::from(event.next_val != 0));
616                let time =
617                    current_time
618                        .checked_add(half)
619                        .ok_or_else(|| SimulatorErrorCode::Runtime {
620                            message: format!("event {event_id} overflows simulation time"),
621                            signals: Vec::new(),
622                        })?;
623                self.push_periodic_event(SimEvent {
624                    time,
625                    event_ref: event.event_ref,
626                    signal: event.signal,
627                    next_val: 1 - event.next_val,
628                });
629            }
630        }
631
632        Ok(())
633    }
634
635    pub fn time(&self) -> u64 {
636        self.scheduler.time
637    }
638
639    pub fn set_time(&mut self, time: u64) {
640        self.scheduler.time = time;
641    }
642
643    /// Time of the next scheduled event or process wakeup. `None` once a
644    /// process has finished the simulation.
645    pub fn next_event_time(&self) -> Option<u64> {
646        if self.finished {
647            return None;
648        }
649        let process = self.process_wakeups.peek().map(|&Reverse((time, _))| time);
650        match (self.scheduler.next_event_time(), process) {
651            (Some(event), Some(process)) => Some(event.min(process)),
652            (event, process) => event.or(process),
653        }
654    }
655
656    /// Periodic clocks as (event id, period).
657    pub fn clock_periods(&self) -> Vec<(usize, u64)> {
658        self.scheduler
659            .clocks
660            .iter()
661            .enumerate()
662            .filter_map(|(id, clock)| Some((id, clock.as_ref()?.period.max(2))))
663            .collect()
664    }
665
666    /// Capture the mutable scheduling state: time, pending events, clocks and
667    /// the clock values edge detection compares against.
668    pub fn snapshot(&self) -> SimulationSnapshot<B> {
669        SimulationSnapshot {
670            time: self.scheduler.time,
671            clocks: self.scheduler.clocks.clone(),
672            event_queue: self.scheduler.event_queue.clone(),
673            periodic_events: self.periodic_events.clone(),
674            last_clock_values: self.last_clock_values.clone(),
675            process_wakeups: self.process_wakeups.clone(),
676            finished: self.finished,
677        }
678    }
679
680    /// Return to a state captured by [`Self::snapshot`] on a state built for
681    /// the same design.
682    ///
683    /// Event handles may point into the compiled code of the instance that took
684    /// the snapshot, so `remap` translates each pending event into a handle of
685    /// this instance. If an event cannot be translated, nothing is changed and
686    /// that event is returned.
687    pub fn restore(
688        &mut self,
689        snapshot: &SimulationSnapshot<B>,
690        mut remap: impl FnMut(B::Event) -> Option<B::Event>,
691    ) -> Result<(), B::Event> {
692        let event_queue = snapshot
693            .event_queue
694            .iter()
695            .map(|event| {
696                Ok(SimEvent {
697                    event_ref: remap(event.event_ref).ok_or(event.event_ref)?,
698                    ..event.clone()
699                })
700            })
701            .collect::<Result<_, B::Event>>()?;
702        self.scheduler.time = snapshot.time;
703        self.scheduler.clocks.clone_from(&snapshot.clocks);
704        self.scheduler.event_queue = event_queue;
705        self.periodic_events.clone_from(&snapshot.periodic_events);
706        self.last_clock_values
707            .clone_from(&snapshot.last_clock_values);
708        self.process_wakeups.clone_from(&snapshot.process_wakeups);
709        self.finished = snapshot.finished;
710        Ok(())
711    }
712}
713
714/// Scheduling state expressed with the event handles of one backend, so a
715/// caller can translate it to and from names.
716pub struct ScheduleParts<B: SimBackend> {
717    pub time: u64,
718    /// Periodic clocks and their periods.
719    pub clocks: Vec<(B::Event, u64)>,
720    /// Pending events, including those of periodic clocks.
721    pub events: Vec<SimEvent<B>>,
722    /// Pending events that a periodic clock re-schedules when they fire, with
723    /// the number of identical such events.
724    pub periodic: Vec<(SimEvent<B>, u64)>,
725    /// Events whose signal was high when edge detection last sampled it.
726    pub high_events: Vec<B::Event>,
727    /// Suspended processes and the time each resumes at.
728    pub process_wakeups: Vec<(usize, u64)>,
729    /// A process requested the end of the simulation.
730    pub finished: bool,
731}
732
733impl<B: SimBackend> SimulationState<B> {
734    /// Express the scheduling state with `backend`'s event handles.
735    pub fn export_schedule(&self, backend: &B) -> ScheduleParts<B> {
736        let events = backend.id_to_event_slice();
737        ScheduleParts {
738            time: self.scheduler.time,
739            clocks: self
740                .scheduler
741                .clocks
742                .iter()
743                .enumerate()
744                .filter_map(|(id, clock)| Some((events[id], clock.as_ref()?.period)))
745                .collect(),
746            events: self.scheduler.event_queue.iter().cloned().collect(),
747            periodic: self
748                .periodic_events
749                .iter()
750                .map(|(key, &count)| {
751                    (
752                        SimEvent {
753                            time: key.time,
754                            event_ref: events[key.event_id],
755                            signal: key.signal,
756                            next_val: key.next_val,
757                        },
758                        count as u64,
759                    )
760                })
761                .collect(),
762            high_events: self.last_clock_values.iter().map(|id| events[id]).collect(),
763            process_wakeups: {
764                let mut wakeups = self
765                    .process_wakeups
766                    .iter()
767                    .map(|&Reverse((time, process))| (process, time))
768                    .collect::<Vec<_>>();
769                wakeups.sort_unstable();
770                wakeups
771            },
772            finished: self.finished,
773        }
774    }
775
776    /// Replace the scheduling state with `parts`, whose handles belong to
777    /// this state's backend.
778    pub fn import_schedule(&mut self, parts: ScheduleParts<B>) {
779        self.scheduler.time = parts.time;
780        self.scheduler.clocks.clear();
781        for (event, period) in parts.clocks {
782            let id = event.id();
783            if id >= self.scheduler.clocks.len() {
784                self.scheduler.clocks.resize(id + 1, None);
785            }
786            self.scheduler.clocks[id] = Some(ClockDef { period });
787        }
788        self.scheduler.event_queue = parts.events.into_iter().collect();
789        self.periodic_events = parts
790            .periodic
791            .into_iter()
792            .map(|(event, count)| (PeriodicEventKey::from_event(&event), count as usize))
793            .collect();
794        self.last_clock_values.make_empty();
795        for event in parts.high_events {
796            self.last_clock_values.insert(event.id());
797        }
798        self.process_wakeups = parts
799            .process_wakeups
800            .into_iter()
801            .map(|(process, time)| Reverse((time, process)))
802            .collect();
803        self.finished = parts.finished;
804    }
805}
806
807/// Scheduling state captured by [`SimulationState::snapshot`].
808pub struct SimulationSnapshot<B: SimBackend> {
809    time: u64,
810    clocks: Vec<Option<ClockDef>>,
811    event_queue: std::collections::BinaryHeap<SimEvent<B>>,
812    periodic_events: FxHashMap<PeriodicEventKey, usize>,
813    last_clock_values: BitSet,
814    process_wakeups: BinaryHeap<ProcessWakeup>,
815    finished: bool,
816}
817
818impl<B: SimBackend> Clone for SimulationSnapshot<B> {
819    fn clone(&self) -> Self {
820        Self {
821            time: self.time,
822            clocks: self.clocks.clone(),
823            event_queue: self.event_queue.clone(),
824            periodic_events: self.periodic_events.clone(),
825            last_clock_values: self.last_clock_values.clone(),
826            process_wakeups: self.process_wakeups.clone(),
827            finished: self.finished,
828        }
829    }
830}
831
832impl<B: SimBackend> SimulationSnapshot<B> {
833    /// Simulation time at which the snapshot was taken.
834    pub fn time(&self) -> u64 {
835        self.time
836    }
837}