Skip to main content

celox_runtime/
backend.rs

1use std::{any::Any, sync::Arc};
2
3use num_bigint::BigUint;
4
5pub use crate::SimulatorErrorCode;
6use crate::{AbsoluteAddr, MemoryLayout, RuntimeEventBuffer, SignalRef};
7
8/// Marker trait for backend-specific event handles.
9///
10/// An event handle is an opaque reference to a compiled clock or
11/// async-reset trigger. It is resolved once via
12/// [`SimBackend::resolve_event`] and then passed to tick/eval methods
13/// for zero-cost dispatch.
14pub trait EventHandle: Copy + std::fmt::Debug {
15    /// Numeric event identifier used for scheduling.
16    fn id(&self) -> usize;
17
18    /// The absolute address of the signal this event is bound to.
19    fn addr(&self) -> AbsoluteAddr;
20}
21
22/// Abstraction over different simulation backends (JIT, WASM, etc.).
23///
24/// `Simulator<B>` is generic over this trait so that the same high-level
25/// API works with any backend. `JitBackend` is the default.
26pub trait SimBackend {
27    /// The event handle type produced by this backend.
28    type Event: EventHandle;
29
30    // ── evaluation ──────────────────────────────────────────────
31    fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode>;
32
33    /// Evaluate and apply a flip-flop domain for the given event.
34    fn eval_apply_ff_at(&mut self, event: Self::Event) -> Result<(), SimulatorErrorCode>;
35
36    /// Evaluate combinational logic and then evaluate/apply one flip-flop
37    /// domain. Backends may override this to compile the two phases as one
38    /// function; the default preserves the same ordering with two calls.
39    fn eval_comb_apply_ff_at(&mut self, event: Self::Event) -> Result<(), SimulatorErrorCode> {
40        self.eval_comb()?;
41        self.eval_apply_ff_at(event)
42    }
43
44    /// Execute up to `count` identical fused ticks. The returned count is the
45    /// number of iterations completed before a runtime event or error forced a
46    /// return to the host. Backends without an in-function loop execute one
47    /// iteration so the caller can preserve per-tick observation semantics.
48    fn eval_comb_apply_ff_many_at(
49        &mut self,
50        event: Self::Event,
51        count: u64,
52    ) -> (u64, Result<(), SimulatorErrorCode>) {
53        if count == 0 {
54            return (0, Ok(()));
55        }
56        (1, self.eval_comb_apply_ff_at(event))
57    }
58
59    /// Evaluate FF domain without applying (for cascaded clocks).
60    fn eval_only_ff_at(&mut self, event: Self::Event) -> Result<(), SimulatorErrorCode>;
61
62    /// Apply (commit) an already-evaluated FF domain.
63    fn apply_ff_at(&mut self, event: Self::Event) -> Result<(), SimulatorErrorCode>;
64
65    /// Run process kernel `index` until it suspends or ends. The kernel
66    /// reports how it returned in its control slots. Backends without
67    /// process kernels reject the call.
68    fn run_process(&mut self, index: usize) -> Result<(), SimulatorErrorCode> {
69        Err(SimulatorErrorCode::Runtime {
70            message: format!("backend cannot run process kernel {index}"),
71            signals: Vec::new(),
72        })
73    }
74
75    // ── signal access ───────────────────────────────────────────
76    fn resolve_signal(&self, addr: &AbsoluteAddr) -> SignalRef;
77    fn resolve_event(&self, addr: &AbsoluteAddr) -> Self::Event;
78    fn resolve_event_opt(&self, addr: &AbsoluteAddr) -> Option<Self::Event>;
79    fn resolve_eval_only_event(&self, addr: &AbsoluteAddr) -> Option<Self::Event>;
80    fn resolve_apply_event(&self, addr: &AbsoluteAddr) -> Option<Self::Event>;
81
82    // ── get / set ───────────────────────────────────────────────
83    fn set<T: Copy>(&mut self, signal: SignalRef, val: T);
84    fn set_wide(&mut self, signal: SignalRef, val: BigUint);
85    fn set_four_state(&mut self, signal: SignalRef, val: BigUint, mask: BigUint);
86    fn get(&self, signal: SignalRef) -> BigUint;
87    fn get_as<T: Default + Copy>(&self, signal: SignalRef) -> T;
88    fn get_four_state(&self, signal: SignalRef) -> (BigUint, BigUint);
89
90    // ── memory / layout ─────────────────────────────────────────
91    fn memory_as_ptr(&self) -> (*const u8, usize);
92    /// Exposing a retained writable view must permanently disable sparse VCD
93    /// tracking in backend-owned state outside the returned memory range.
94    fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize);
95    /// Overwrite `bytes.len()` bytes of the memory image at `offset`.
96    ///
97    /// Unlike [`Self::memory_as_mut_ptr`], this keeps VCD change tracking
98    /// enabled. The write marks no VCD activity, so the caller must have the
99    /// next dump scan every signal.
100    fn write_memory(&mut self, offset: usize, bytes: &[u8]) {
101        let (ptr, len) = self.memory_as_mut_ptr();
102        assert!(offset + bytes.len() <= len, "write past the memory image");
103        // Safety: the backend exposes `len` writable bytes at `ptr`.
104        unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(offset), bytes.len()) };
105    }
106
107    /// Return an opaque owner that keeps the memory allocation alive.
108    ///
109    /// Host integrations that expose the raw memory outside Rust can retain
110    /// this value until their external view is finalized. Backends without a
111    /// separately owned stable allocation may keep the default `None`.
112    /// When returning `Some`, the allocation and address reported by
113    /// [`Self::memory_as_ptr`] and [`Self::memory_as_mut_ptr`] must remain valid
114    /// until every clone of the owner has been dropped.
115    fn memory_owner(&self) -> Option<Arc<dyn Any + Send + Sync>> {
116        None
117    }
118    fn runtime_event_buffer_as_ptr(&self) -> (*const u8, usize);
119    fn runtime_event_buffer(&self) -> Option<Arc<RuntimeEventBuffer>> {
120        None
121    }
122    fn set_comb_capture_event_enabled(&mut self, _active_sites: &[bool]) {}
123    fn stable_region_size(&self) -> usize;
124    fn layout(&self) -> &MemoryLayout;
125
126    // ── event enumeration ───────────────────────────────────────
127    fn id_to_addr_slice(&self) -> &[AbsoluteAddr];
128    fn id_to_event_slice(&self) -> &[Self::Event];
129    fn num_events(&self) -> usize;
130
131    // ── trigger bits (for Simulation edge detection) ────────────
132    fn clear_triggered_bits(&mut self);
133    fn mark_triggered_bit(&mut self, id: usize);
134    fn get_triggered_bits(&self) -> bit_set::BitSet;
135
136    /// Opt into generated write notifications only while no raw mutable view
137    /// has escaped. The default keeps other backend implementations correct.
138    fn vcd_tracking_enabled(&self) -> bool {
139        false
140    }
141
142    /// Consume waveform activity independently of clock trigger processing.
143    /// Returns false when full scanning is required (including raw host views).
144    fn take_vcd_activity(&mut self, groups: &mut Vec<usize>) -> bool {
145        groups.clear();
146        if !self.vcd_tracking_enabled() {
147            return false;
148        }
149        let Some(trace) = self.layout().trace.as_ref() else {
150            return false;
151        };
152        let (ptr, size) = self.memory_as_ptr();
153        // SAFETY: &mut self excludes execution/host access during consumption;
154        // the backend memory contract exposes this same writable state image.
155        let memory = unsafe { std::slice::from_raw_parts_mut(ptr.cast_mut(), size) };
156        trace.take(memory, groups);
157        true
158    }
159
160    /// Called by host setters, which share the generated-store notification ABI.
161    fn mark_vcd_signal(&mut self, signal: SignalRef) {
162        if let Some(trace) = self.layout().trace.as_ref() {
163            let len = signal.array_layout.map_or_else(
164                || celox_state_layout::get_byte_size(signal.width),
165                |array| array.plane_size,
166            );
167            let (ptr, _) = self.memory_as_ptr();
168            // SAFETY: layout homes and metadata fit in the writable state image.
169            unsafe {
170                trace.mark_range(ptr.cast_mut(), signal.offset, len);
171            }
172        }
173    }
174}