vj-sim 0.2.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! The embedder's view of thread life cycle (IEEE 1800-2023 37.44, 38.36.1:
//! `cbStartOfThread`, `cbEnterThread`, `cbEndOfThread`).
//!
//! A *thread* is a process the scheduler runs: a static procedure
//! (`initial`/`always*`/`final`) or one branch of a `fork`. The scheduler
//! reports three moments of a thread's life to one installed hook:
//!
//! * [`ThreadEvent::Start`]: the thread is created. A static process is
//!   created when it first runs; a fork branch when its `fork` executes
//!   (before the branch runs, and before the parent continues).
//! * [`ThreadEvent::Enter`]: the thread resumes after having been suspended
//!   (a delay elapsed, an event fired, a join was satisfied, ...). Its first
//!   run is its start, not a resume.
//! * [`ThreadEvent::End`]: the thread is deleted, because it finished or was
//!   killed (`disable`, `disable fork`, `process::kill`).
//!
//! The hook is per OS thread, like the rest of a run's ambient state, and
//! absent unless an embedder installs it: with none installed every point is
//! one thread-local read.

use std::cell::Cell;

use super::{ProcessId, SchedulerOps, SpawnSpec};

/// One moment of a thread's life.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ThreadEvent {
    /// `pid` was created; `parent` is the process whose `fork` created it
    /// and `spawn` the branch it runs (a static process has neither).
    Start {
        pid: ProcessId,
        parent: Option<ProcessId>,
        spawn: Option<SpawnSpec>,
    },
    /// `pid` is about to run again after a suspension.
    Enter { pid: ProcessId },
    /// `pid` is gone; `killed` is false when it ran to completion.
    End { pid: ProcessId, killed: bool },
}

/// The installed hook; it gets the engine's ops, which are also installed as
/// the current ops for the call, so a callback it runs can write.
pub type ThreadHook = fn(&dyn SchedulerOps, ThreadEvent);

thread_local! {
    static HOOK: Cell<Option<ThreadHook>> = const { Cell::new(None) };
}

/// Installs (or, with `None`, removes) this thread's hook.
pub fn set_thread_hook(hook: Option<ThreadHook>) {
    HOOK.with(|h| h.set(hook));
}

/// Whether a hook is installed: the guard of every scheduler call site.
#[inline]
pub fn thread_hook_installed() -> bool {
    HOOK.with(|h| h.get().is_some())
}

/// Reports `event` to the installed hook, if any.
pub fn fire_thread_event(ops: &dyn SchedulerOps, event: ThreadEvent) {
    if let Some(hook) = HOOK.with(Cell::get) {
        let pid = match event {
            ThreadEvent::Start { pid, .. }
            | ThreadEvent::Enter { pid }
            | ThreadEvent::End { pid, .. } => pid,
        };
        // The thread the event is about is the current one for the hook's
        // duration (what `vpi_handle(vpiThread, NULL)` answers).
        super::with_scheduler_ops(ops, || {
            super::with_current_process(pid, || hook(ops, event));
        });
    }
}