celox 0.4.1

Celox HDL Simulator
Documentation
#![cfg(feature = "host-runtime")]

use std::sync::{Arc, Mutex};

use celox::{
    InjectedCall, InjectedComponentHandler, InjectedComponents, InjectedHook, InjectedNamedValue,
    InjectedResult, InjectedValue, Simulator, TestResult,
};

struct Model {
    phases: Arc<Mutex<Vec<String>>>,
    fired_clocks: Arc<Mutex<Vec<Option<String>>>>,
}

impl InjectedComponentHandler for Model {
    fn call(&self, call: InjectedCall) -> Result<InjectedResult, String> {
        let phase = match call.hook {
            InjectedHook::Create => "create",
            InjectedHook::Init => "init",
            InjectedHook::Clock => "clock",
            InjectedHook::Finish => "finish",
            InjectedHook::Reset => "reset",
            InjectedHook::Method { .. } => "method",
        };
        self.phases.lock().unwrap().push(phase.into());
        self.fired_clocks
            .lock()
            .unwrap()
            .push(call.fired_clock.clone());
        let output = match call.hook {
            InjectedHook::Init => Some(0),
            InjectedHook::Clock => {
                let input = call
                    .inputs
                    .iter()
                    .find(|value| value.name == "d")
                    .and_then(|value| match &value.value {
                        InjectedValue::Bits { words, .. } => words.first().copied(),
                        _ => None,
                    })
                    .unwrap();
                Some(input + 3)
            }
            _ => None,
        };
        Ok(InjectedResult {
            outputs: output
                .map(|value| InjectedNamedValue {
                    name: "q".into(),
                    value: InjectedValue::Bits {
                        words: vec![value],
                        mask_xz: vec![0],
                        width: 8,
                    },
                })
                .into_iter()
                .collect(),
            ..Default::default()
        })
    }
}

#[test]
fn injected_clocked_component_uses_component_scheduling() {
    let source = r#"
        #[test(InjectedComponentTb)]
        module InjectedComponentTb {
            inst clk: $tb::clock_gen;
            var d: logic<8>;
            var q: logic<8>;
            inst model: $comp::ts_model (clk, d, q);

            initial {
                $assert(q == 0, "on_init output");
                d = 8'h20;
                clk.next();
                $assert(q == 8'h23, "injected component output");
                $finish();
            }
        }
    "#;
    let phases = Arc::new(Mutex::new(Vec::new()));
    let fired_clocks = Arc::new(Mutex::new(Vec::new()));
    let mut components = InjectedComponents::new();
    components
        .insert(
            "ts_model",
            r#"{
                "kind":"clocked",
                "ports":[
                    {"name":"clk","dir":"input","role":"clock"},
                    {"name":"d","dir":"input"},
                    {"name":"q","dir":"output"}
                ]
            }"#,
            Arc::new(Model {
                phases: phases.clone(),
                fired_clocks: fired_clocks.clone(),
            }),
        )
        .unwrap();

    let result = Simulator::builder(source, "InjectedComponentTb")
        .with_injected_components(components)
        .run_test()
        .unwrap();
    assert_eq!(result, TestResult::Pass, "{result:?}");
    assert_eq!(
        *phases.lock().unwrap(),
        ["create", "init", "clock", "finish"]
    );
    assert_eq!(
        *fired_clocks.lock().unwrap(),
        [None, None, Some("clk".to_string()), None]
    );
}