celox 0.4.1

Celox HDL Simulator
Documentation
use celox::{RuntimeErrorCode, SimulatorBuilder};

#[path = "test_utils/mod.rs"]
#[macro_use]
#[allow(unused_macros)]
mod test_utils;

all_backends! {

// Converging true loop via assign statements.
// assign passes the analyzer (no UnassignVariable), and true_loop
// declaration allows the SIR scheduler to accept the cycle.
fn test_converging_true_loop_with_assign(sim) {
    @ignore_on(sv);
    @setup { let code = r#"
        module Top (i: input logic<2>, o: output logic<2>) {
            var v: logic<2>;
            assign v[0] = v[1] ^ i[0];
            assign v[1] = v[0] ^ i[1];
            assign o = v;
        }
    "#; }
    @build SimulatorBuilder::new(code, "Top")
        .true_loop(
            (vec![], vec!["v".to_owned()]),
            (vec![], vec!["v".to_owned()]),
            10,
        );

    let i_port = sim.signal("i");
    let o_port = sim.signal("o");

    // i=0b00 → v[0]=v[1]^0, v[1]=v[0]^0 → v[0]=v[1], v[1]=v[0] → converges to 0
    sim.modify(|io| io.set(i_port, 0u8)).unwrap();
    assert_eq!(sim.get(o_port), 0u8.into());
}

// Non-converging true loop: oscillation detected at runtime.
// Uses cross-bit assign to bypass the analyzer's UnassignVariable check.
fn test_true_loop_oscillation_detected(sim) {
    // The Veryl adapter cannot apply Celox's explicit true-loop guard config.
    @omit_veryl;
    @ignore_on(sv);
    @setup {
    // v[0] = ~v[1] & a, v[1] = v[0]
    // When a=1: v[0]=~v[1], v[1]=v[0] → oscillates (0,0)→(1,0)→(1,1)→(0,1)→(0,0)→...
    let code = r#"
        module Top (a: input logic, y: output logic) {
            var v: logic<2>;
            assign v[0] = ~v[1] & a;
            assign v[1] = v[0];
            assign y = v[0];
        }
    "#;
    }
    @build SimulatorBuilder::new(code, "Top")
        .true_loop(
            (vec![], vec!["v".to_string()]),
            (vec![], vec!["v".to_string()]),
            10,
        );

    let id_a = sim.signal("a");
    // a=1 triggers oscillation
    sim.modify(|io| io.set(id_a, 1u8)).unwrap();
    let res = sim.eval_comb();
    assert!(res.is_err());
    let err = res.unwrap_err();
    assert_eq!(err, RuntimeErrorCode::DetectedTrueLoop);
    assert_eq!(err.to_string(), "Detected True Loop: v");
}

fn test_runtime_true_loop_reports_only_failing_scc(sim) {
    // The Veryl adapter cannot apply Celox's explicit true-loop guard config.
    @omit_veryl;
    @ignore_on(sv);
    @setup {
    let code = r#"
        module Top (
            a: input logic,
            b: input logic,
            y: output logic
        ) {
            var v: logic<2>;
            var w: logic<2>;
            assign v[0] = ~v[1] & a;
            assign v[1] = v[0];
            assign w[0] = ~w[1] & b;
            assign w[1] = w[0];
            assign y = v[0] ^ w[0];
        }
    "#;
    }
    @build SimulatorBuilder::new(code, "Top")
        .true_loop(
            (vec![], vec!["v".to_string()]),
            (vec![], vec!["v".to_string()]),
            10,
        )
        .true_loop(
            (vec![], vec!["w".to_string()]),
            (vec![], vec!["w".to_string()]),
            10,
        );

    let id_a = sim.signal("a");
    let id_b = sim.signal("b");

    sim.modify(|io| {
        io.set(id_a, 1u8);
        io.set(id_b, 0u8);
    })
    .unwrap();
    let err = sim.eval_comb().unwrap_err().to_string();
    assert_eq!(err, "Detected True Loop: v");

    sim.modify(|io| {
        io.set(id_a, 0u8);
        io.set(id_b, 1u8);
    })
    .unwrap();
    let err = sim.eval_comb().unwrap_err().to_string();
    assert_eq!(err, "Detected True Loop: w");
}

}