celox 0.4.1

Celox HDL Simulator
Documentation
use celox::{BigUint, RuntimeProgram, WasmBackend};

fn build_wasm(code: &str, top: &str) -> (WasmBackend, RuntimeProgram) {
    let sim = celox::Simulator::builder(code, top).build_wasm().unwrap();
    let program = sim.program().clone();
    (sim.into_backend(), program)
}

#[test]
fn test_wasm_comb_constant_display_runtime_event() {
    let code = r#"
        module Top {
            always_comb {
                $display("hi");
            }
        }
    "#;
    let mut sim = celox::Simulator::builder(code, "Top")
        .build_wasm()
        .expect("build_wasm failed");

    sim.eval_comb().expect("eval_comb failed");
    assert_eq!(
        sim.drain_runtime_events(),
        vec![celox::RuntimeEvent::Display {
            message: "hi".to_string(),
        }],
    );

    sim.eval_comb().expect("second eval_comb failed");
    assert_eq!(sim.drain_runtime_events(), Vec::new());
}

#[test]
fn test_wasm_comb_store_enables_downstream_display() {
    let code = r#"
        module Top (
            a: input logic<8>,
        ) {
            var x: logic<8>;

            always_comb {
                x = a;
            }

            always_comb {
                $display("x=%0d", x);
            }
        }
    "#;
    let mut sim = celox::Simulator::builder(code, "Top")
        .build_wasm()
        .expect("build_wasm failed");
    let a = sim.signal("a");

    sim.drain_runtime_events();
    sim.modify(|io| io.set(a, 7u8)).expect("modify failed");
    sim.eval_comb().expect("eval_comb failed");
    assert_eq!(
        sim.drain_runtime_events(),
        vec![celox::RuntimeEvent::Display {
            message: "x=7".to_string(),
        }],
    );

    sim.modify(|io| io.set(a, 7u8)).expect("same modify failed");
    sim.eval_comb().expect("same eval_comb failed");
    assert_eq!(sim.drain_runtime_events(), Vec::new());
}

#[test]
fn test_wasm_comb_dynamic_store_enables_downstream_display() {
    let code = r#"
        module Top (
            idx: input logic<7>,
            val: input logic<8>,
            unrelated: input logic,
            out: output logic<8>,
        ) {
            var tmp: logic<128>;

            always_comb {
                tmp = 128'd0;
                tmp[idx +: 8] = val;
            }

            always_comb {
                out = tmp[67:60];
                $display("slice=%0d", tmp[67:60]);
            }
        }
    "#;
    let mut sim = celox::Simulator::builder(code, "Top")
        .build_wasm()
        .expect("build_wasm failed");
    let idx = sim.signal("idx");
    let val = sim.signal("val");
    let unrelated = sim.signal("unrelated");
    let out = sim.signal("out");

    sim.drain_runtime_events();
    sim.modify(|io| {
        io.set(idx, 60u8);
        io.set(val, 0xABu8);
    })
    .expect("dynamic modify failed");
    assert_eq!(sim.get_as::<u8>(out), 0xAB);
    assert_eq!(
        sim.drain_runtime_events(),
        vec![celox::RuntimeEvent::Display {
            message: "slice=171".to_string(),
        }],
    );

    sim.modify(|io| io.set(val, 0xABu8))
        .expect("same value modify failed");
    assert_eq!(sim.drain_runtime_events(), Vec::new());

    sim.modify(|io| io.set(unrelated, 1u8))
        .expect("unrelated modify failed");
    assert_eq!(sim.get_as::<u8>(out), 0xAB);
    assert_eq!(sim.drain_runtime_events(), Vec::new());
}

#[test]
fn test_wasm_comb_wide_store_enables_downstream_display() {
    let code = r#"
        module Top (
            a: input logic<128>,
            unrelated: input logic<8>,
            out: output logic<8>,
        ) {
            var tmp: logic<128>;

            always_comb {
                tmp = a;
            }

            always_comb {
                out = tmp[7:0];
                $display("lo=%0d hi=%0d", tmp[7:0], tmp[71:64]);
            }
        }
    "#;
    let mut sim = celox::Simulator::builder(code, "Top")
        .build_wasm()
        .expect("build_wasm failed");
    let a = sim.signal("a");
    let unrelated = sim.signal("unrelated");
    let out = sim.signal("out");

    sim.drain_runtime_events();
    let value: BigUint = (BigUint::from(0x12u8) << 64) | BigUint::from(0x34u8);
    sim.modify(|io| io.set_wide(a, value.clone()))
        .expect("wide modify failed");
    assert_eq!(sim.get_as::<u8>(out), 0x34);
    assert_eq!(
        sim.drain_runtime_events(),
        vec![celox::RuntimeEvent::Display {
            message: "lo=52 hi=18".to_string(),
        }],
    );

    sim.modify(|io| io.set_wide(a, value))
        .expect("same wide modify failed");
    assert_eq!(sim.drain_runtime_events(), Vec::new());

    sim.modify(|io| io.set(unrelated, 1u8))
        .expect("unrelated modify failed");
    assert_eq!(sim.get_as::<u8>(out), 0x34);
    assert_eq!(sim.drain_runtime_events(), Vec::new());
}

#[test]
fn test_wasm_adder_combinational() {
    let code = r#"
        module Top (
            a: input  logic<8>,
            b: input  logic<8>,
            s: output logic<8>
        ) {
            assign s = a + b;
        }
    "#;
    let (mut backend, program) = build_wasm(code, "Top");

    let a_addr = program.get_addr(&[], &["a"]).unwrap();
    let b_addr = program.get_addr(&[], &["b"]).unwrap();
    let s_addr = program.get_addr(&[], &["s"]).unwrap();

    let a_sig = backend.resolve_signal(&a_addr);
    let b_sig = backend.resolve_signal(&b_addr);
    let s_sig = backend.resolve_signal(&s_addr);

    backend.set(a_sig, 10u8);
    backend.set(b_sig, 20u8);
    backend.eval_comb().expect("eval_comb failed");

    let result: u8 = backend.get_as(s_sig);
    assert_eq!(result, 30, "Expected 10 + 20 = 30, got {result}");
}

#[test]
fn test_wasm_mux_branching() {
    let code = r#"
        module Top (
            sel: input  logic,
            a:   input  logic<8>,
            b:   input  logic<8>,
            y:   output logic<8>
        ) {
            always_comb {
                if sel {
                    y = a;
                } else {
                    y = b;
                }
            }
        }
    "#;
    let (mut backend, program) = build_wasm(code, "Top");

    let sel_addr = program.get_addr(&[], &["sel"]).unwrap();
    let a_addr = program.get_addr(&[], &["a"]).unwrap();
    let b_addr = program.get_addr(&[], &["b"]).unwrap();
    let y_addr = program.get_addr(&[], &["y"]).unwrap();

    let sel = backend.resolve_signal(&sel_addr);
    let a = backend.resolve_signal(&a_addr);
    let b = backend.resolve_signal(&b_addr);
    let y = backend.resolve_signal(&y_addr);

    backend.set(sel, 1u8);
    backend.set(a, 42u8);
    backend.set(b, 99u8);
    backend.eval_comb().unwrap();
    assert_eq!(backend.get_as::<u8>(y), 42);

    backend.set(sel, 0u8);
    backend.eval_comb().unwrap();
    assert_eq!(backend.get_as::<u8>(y), 99);
}

#[test]
fn test_wasm_counter_sequential() {
    let code = r#"
        module Top (
            clk: input  '_ clock,
            rst: input  '_ reset_async_low,
            en:  input  logic,
            cnt: output logic<8>
        ) {
            var count: logic<8>;
            assign cnt = count;
            always_ff (clk, rst) {
                if_reset {
                    count = 0;
                } else if en {
                    count = count + 1;
                }
            }
        }
    "#;
    let (mut backend, program) = build_wasm(code, "Top");

    let clk_addr = program.get_addr(&[], &["clk"]).unwrap();
    let rst_addr = program.get_addr(&[], &["rst"]).unwrap();
    let en_addr = program.get_addr(&[], &["en"]).unwrap();
    let cnt_addr = program.get_addr(&[], &["cnt"]).unwrap();

    let rst_sig = backend.resolve_signal(&rst_addr);
    let en_sig = backend.resolve_signal(&en_addr);
    let cnt_sig = backend.resolve_signal(&cnt_addr);
    let clk_ev = backend.resolve_event(&clk_addr);

    backend.set(rst_sig, 0u8);
    backend.set(en_sig, 0u8);
    backend.eval_comb().unwrap();
    backend.eval_apply_ff_at(&clk_ev).unwrap();
    backend.eval_comb().unwrap();
    assert_eq!(backend.get_as::<u8>(cnt_sig), 0);

    backend.set(rst_sig, 1u8);
    backend.set(en_sig, 1u8);
    for i in 1..=5u8 {
        backend.eval_comb().unwrap();
        backend.eval_apply_ff_at(&clk_ev).unwrap();
        backend.eval_comb().unwrap();
        assert_eq!(backend.get_as::<u8>(cnt_sig), i, "tick {i}");
    }
}

#[test]
fn test_wasm_wide_value_128bit() {
    let code = r#"
        module Top (
            a: input  logic<128>,
            b: input  logic<128>,
            s: output logic<128>
        ) {
            assign s = a + b;
        }
    "#;
    let (mut backend, program) = build_wasm(code, "Top");

    let a_addr = program.get_addr(&[], &["a"]).unwrap();
    let b_addr = program.get_addr(&[], &["b"]).unwrap();
    let s_addr = program.get_addr(&[], &["s"]).unwrap();

    let a_sig = backend.resolve_signal(&a_addr);
    let b_sig = backend.resolve_signal(&b_addr);
    let s_sig = backend.resolve_signal(&s_addr);

    backend.set_wide(a_sig, BigUint::from(100u64));
    backend.set_wide(b_sig, BigUint::from(200u64));
    backend.eval_comb().unwrap();
    assert_eq!(backend.get(s_sig), BigUint::from(300u64));

    let big_a = BigUint::from(u64::MAX);
    let big_b = BigUint::from(1u64);
    backend.set_wide(a_sig, big_a.clone());
    backend.set_wide(b_sig, big_b.clone());
    backend.eval_comb().unwrap();
    assert_eq!(backend.get(s_sig), &big_a + &big_b);
}