celox 0.4.5

Celox HDL Simulator
Documentation
//! Adapter that wraps `veryl_simulator::Simulator` with an API compatible
//! with Celox's `Simulator<B>`, so that the same test body produced by
//! `all_backends!` compiles for the Veryl reference backend.
#![allow(dead_code)]

use celox::{AddrLookupError, RuntimeErrorCode};
use num_bigint::BigUint;
use std::path::Path;
use veryl_analyzer::ir as air;
use veryl_analyzer::value::{Value, ValueBigUint, ValueU64};
use veryl_analyzer::{Analyzer, Context, attribute_table, symbol_table};
use veryl_metadata::Metadata;
use veryl_parser::Parser;
use veryl_simulator::Simulator as VerylSim;
use veryl_simulator::assert_buffer;
use veryl_simulator::ir::{Config, Event, build_ir};

// ---------------------------------------------------------------------------
// Handle types (Copy, like Celox's SignalRef / EventRef)
// ---------------------------------------------------------------------------

#[derive(Clone, Copy, Debug)]
pub struct VerylSignalRef(usize);

#[derive(Clone, Copy, Debug)]
pub struct VerylEventRef(usize);

// ---------------------------------------------------------------------------
// IO context (for `modify(|io| io.set(...))`)
// ---------------------------------------------------------------------------

pub struct VerylIOContext<'a> {
    sim: &'a mut VerylSim,
    names: &'a [String],
    input_written: &'a mut bool,
}

impl VerylIOContext<'_> {
    fn note_input_write(&mut self) {
        *self.input_written = true;
    }

    pub fn set<T: Copy>(&mut self, signal: VerylSignalRef, val: T) {
        self.note_input_write();
        let name = &self.names[signal.0];
        self.sim.set(name, t_to_value(val));
    }

    pub fn set_wide(&mut self, signal: VerylSignalRef, val: BigUint) {
        self.note_input_write();
        let name = &self.names[signal.0];
        let width = val.bits() as usize;
        self.sim
            .set(name, Value::new_biguint(val, width.max(1), false));
    }

    pub fn set_four_state(&mut self, signal: VerylSignalRef, val: BigUint, mask: BigUint) {
        self.note_input_write();
        let name = &self.names[signal.0];
        let width = self
            .sim
            .get(name)
            .or_else(|| self.sim.get_var(name))
            .unwrap_or_else(|| panic!("signal '{name}' not found in veryl-simulator"))
            .width();
        self.sim.set(name, four_state_value(val, mask, width));
    }
}

// ---------------------------------------------------------------------------
// Value conversion helpers
// ---------------------------------------------------------------------------

fn t_to_value<T: Copy>(val: T) -> Value {
    let size = std::mem::size_of::<T>();
    let width = size * 8;
    let mut payload = 0u64;
    unsafe {
        std::ptr::copy_nonoverlapping(
            &val as *const T as *const u8,
            &mut payload as *mut u64 as *mut u8,
            size.min(8),
        );
    }
    Value::new(payload, width, false)
}

fn value_to_biguint(v: Value) -> BigUint {
    v.payload().into_owned()
}

fn four_state_value(payload: BigUint, mask: BigUint, width: usize) -> Value {
    // Celox encodes X as (1, 1) and Z as (0, 1), while Veryl uses the
    // opposite payload bit for masked values. Translate at the adapter boundary.
    let payload = payload ^ &mask;
    if width <= 64 {
        let payload = payload.to_u64_digits().first().copied().unwrap_or(0);
        let mask_xz = mask.to_u64_digits().first().copied().unwrap_or(0);
        Value::U64(ValueU64 {
            payload,
            mask_xz,
            width: width as u32,
            signed: false,
        })
    } else {
        Value::BigUint(ValueBigUint {
            payload: Box::new(payload),
            mask_xz: Box::new(mask),
            width: width as u32,
            signed: false,
        })
    }
}

fn is_non_progressing_loop_diagnostic(message: &str) -> bool {
    let Some(rest) =
        message.strip_prefix("for-loop step does not advance the loop variable (stuck at ")
    else {
        return false;
    };
    let Some((stuck_at, location)) = rest.split_once(") at ") else {
        return false;
    };
    if stuck_at.parse::<u64>().is_err() {
        return false;
    }

    let mut location = location.rsplitn(3, ':');
    let Some(column) = location.next() else {
        return false;
    };
    let Some(line) = location.next() else {
        return false;
    };
    let Some(_source) = location.next() else {
        return false;
    };
    line.parse::<u32>().is_ok() && column.parse::<u32>().is_ok()
}

fn take_runtime_error() -> Result<(), RuntimeErrorCode> {
    if !assert_buffer::has_fatal() {
        // `$assert_continue` reports are not execution errors for this adapter,
        // but must not leak into the next comparison test.
        let _ = assert_buffer::take_failure();
        return Ok(());
    }

    let Some(message) = assert_buffer::take_failure() else {
        return Ok(());
    };
    if is_non_progressing_loop_diagnostic(&message) {
        Err(RuntimeErrorCode::DetectedTrueLoop)
    } else {
        Err(RuntimeErrorCode::Runtime {
            message,
            signals: Vec::new(),
        })
    }
}

// ---------------------------------------------------------------------------
// Adapter
// ---------------------------------------------------------------------------

pub struct VerylSimAdapter {
    sim: VerylSim,
    /// The constructor settles once so undriven outputs are readable. Keep its
    /// diagnostics until either the caller observes them or drives an input.
    initial_diagnostics_pending: bool,
    /// Signal name table: VerylSignalRef(i) → names[i]
    names: Vec<String>,
    /// Event table: VerylEventRef(i) → events[i]
    events: Vec<Event>,
}

impl VerylSimAdapter {
    fn discard_initial_diagnostics(&mut self) {
        if self.initial_diagnostics_pending {
            assert_buffer::reset();
            self.initial_diagnostics_pending = false;
        }
    }

    fn finish_runtime_operation(&mut self) -> Result<(), RuntimeErrorCode> {
        self.initial_diagnostics_pending = false;
        take_runtime_error()
    }

    pub fn signal(&mut self, name: &str) -> VerylSignalRef {
        // Reuse existing entry if present
        if let Some(idx) = self.names.iter().position(|n| n == name) {
            return VerylSignalRef(idx);
        }
        let idx = self.names.len();
        self.names.push(name.to_string());
        VerylSignalRef(idx)
    }

    pub fn event(&mut self, port: &str) -> VerylEventRef {
        let ev = self
            .sim
            .get_clock(port)
            .unwrap_or_else(|| panic!("event '{port}' not found in veryl-simulator"));
        let idx = self.events.len();
        self.events.push(ev);
        VerylEventRef(idx)
    }

    pub fn modify<F>(&mut self, f: F) -> Result<(), RuntimeErrorCode>
    where
        F: FnOnce(&mut VerylIOContext<'_>),
    {
        let mut input_written = false;
        {
            let mut ctx = VerylIOContext {
                sim: &mut self.sim,
                names: &self.names,
                input_written: &mut input_written,
            };
            f(&mut ctx);
        }
        if input_written && self.initial_diagnostics_pending {
            // The constructor settled with provisional input values. Replace
            // those diagnostics with a forced settle after the first write;
            // constant processes must run again even though they do not depend
            // on the input that changed.
            assert_buffer::reset();
            self.initial_diagnostics_pending = false;
            self.sim.mark_comb_dirty();
            self.sim.ensure_comb_updated();
        }
        self.finish_runtime_operation()
    }

    pub fn get(&mut self, signal: VerylSignalRef) -> BigUint {
        let name = &self.names[signal.0];
        if let Some(v) = self.sim.get(name) {
            return value_to_biguint(v);
        }
        if let Some(v) = self.sim.get_var(name) {
            return value_to_biguint(v);
        }
        panic!("signal '{name}' not found in veryl-simulator");
    }

    pub fn get_as<T: Default + Copy>(&mut self, signal: VerylSignalRef) -> T {
        let biguint = self.get(signal);
        let mut result = T::default();
        let bytes = biguint.to_bytes_le();
        let size = std::mem::size_of::<T>();
        let copy_len = bytes.len().min(size);
        unsafe {
            std::ptr::copy_nonoverlapping(
                bytes.as_ptr(),
                &mut result as *mut T as *mut u8,
                copy_len,
            );
        }
        result
    }

    pub fn get_four_state(&mut self, signal: VerylSignalRef) -> (BigUint, BigUint) {
        let name = &self.names[signal.0];
        let value = self
            .sim
            .get(name)
            .or_else(|| self.sim.get_var(name))
            .unwrap_or_else(|| panic!("signal '{name}' not found in veryl-simulator"));
        let mask = value.mask_xz().into_owned();
        (value.payload().into_owned() ^ &mask, mask)
    }

    pub fn tick(&mut self, event: VerylEventRef) -> Result<(), RuntimeErrorCode> {
        self.sim.step(&self.events[event.0]);
        self.finish_runtime_operation()
    }

    pub fn set<T: Copy>(&mut self, signal: VerylSignalRef, val: T) {
        self.discard_initial_diagnostics();
        let name = &self.names[signal.0];
        self.sim.set(name, t_to_value(val));
        self.sim.mark_comb_dirty();
    }

    pub fn set_wide(&mut self, signal: VerylSignalRef, val: BigUint) {
        self.discard_initial_diagnostics();
        let name = &self.names[signal.0];
        let width = val.bits() as usize;
        self.sim
            .set(name, Value::new_biguint(val, width.max(1), false));
        self.sim.mark_comb_dirty();
    }

    pub fn child_signal(&mut self, instance_path: &[(&str, usize)], var: &str) -> VerylSignalRef {
        let mut parts = Vec::new();
        for (name, _idx) in instance_path {
            parts.push(*name);
        }
        parts.push(var);
        let joined = parts.join(".");
        self.signal(&joined)
    }

    pub fn try_signal(&mut self, name: &str) -> Result<VerylSignalRef, AddrLookupError> {
        Ok(self.signal(name))
    }

    pub fn eval_comb(&mut self) -> Result<(), RuntimeErrorCode> {
        self.sim.ensure_comb_updated();
        self.finish_runtime_operation()
    }

    pub fn try_event(&mut self, port: &str) -> Result<VerylEventRef, AddrLookupError> {
        self.sim
            .get_clock(port)
            .map(|ev| {
                let idx = self.events.len();
                self.events.push(ev);
                VerylEventRef(idx)
            })
            .ok_or_else(|| AddrLookupError::VariableNotFound {
                path: port.to_string(),
            })
    }
}

// ---------------------------------------------------------------------------
// Builder
// ---------------------------------------------------------------------------

pub fn build_veryl_adapter(
    sources: &[(&str, &Path)],
    top: &str,
    use_4state: bool,
) -> VerylSimAdapter {
    // Clear global tables (same as Celox does)
    symbol_table::clear();
    attribute_table::clear();
    assert_buffer::reset();

    let metadata = Metadata::create_default("prj").unwrap();
    let analyzer = Analyzer::new(&metadata);

    let mut parsers = Vec::new();
    for (code, path) in sources {
        let parsed = Parser::parse(code, path).unwrap();
        analyzer.analyze_pass1("prj", &parsed.veryl);
        parsers.push(parsed);
    }

    Analyzer::analyze_post_pass1();

    let mut context = Context::default();
    let mut ir = air::Ir::default();
    for parsed in &parsers {
        analyzer.analyze_pass2(&parsed.veryl, &mut context, Some(&mut ir));
    }
    Analyzer::analyze_post_pass2(&ir);

    let top_id = veryl_parser::resource_table::insert_str(top);
    let config = Config {
        use_4state,
        use_jit: false,
        ..Default::default()
    };

    let sim_ir = build_ir(&ir, top_id, &config).unwrap_or_else(|e| {
        panic!("veryl-simulator build_ir failed: {e:?}");
    });

    let mut sim = VerylSim::new(sim_ir, None);
    sim.ensure_comb_updated();

    VerylSimAdapter {
        sim,
        initial_diagnostics_pending: true,
        names: Vec::new(),
        events: Vec::new(),
    }
}