celox 0.11.0

Celox HDL Simulator
Documentation
use celox::{CompilationWarning, FrontendDiagnostic, Simulator, render_diagnostic};

fn alias_warnings(code: &str) -> Vec<String> {
    let simulator = Simulator::builder(code, "Top").build().unwrap();
    simulator
        .warnings()
        .iter()
        .filter(|warning| {
            matches!(
                warning,
                CompilationWarning::Frontend(FrontendDiagnostic::UnspecifiedOutputCopyOrder { .. })
            )
        })
        .map(|warning| render_diagnostic(warning))
        .collect()
}

#[test]
fn warns_on_named_outputs_inside_an_expression_without_rejecting_the_call() {
    let warnings = alias_warnings(
        r#"
module Top (tmp: output logic<8>, out: output logic) {
    function write_outputs(first: output logic<8>, second: output logic<8>) -> logic {
        first = 8'd1;
        second = 8'd2;
        return 1'b1;
    }
    always_comb {
        tmp = 0;
        out = write_outputs(second: tmp, first: tmp) & 1'b1;
    }
}
"#,
    );
    assert_eq!(warnings.len(), 1, "{warnings:?}");
    let warning = &warnings[0];
    assert!(warning.contains("unspecified_output_copy_order"));
    assert!(warning.contains("first") && warning.contains("second"));
    assert!(warning.contains("tmp[7:0]"));
    assert!(warning.contains("write_outputs(second: tmp, first: tmp)"));
    assert!(warning.contains("use distinct output destinations"));
}

#[test]
fn warns_on_overlapping_slices_and_concatenations_but_not_disjoint_slices() {
    for (actuals, expected) in [
        ("word[7:0], word[11:4]", 1),
        ("{word[15:12], word[3:0]}, word[7:0]", 1),
        ("word[7:0], word[15:8]", 0),
    ] {
        let code = format!(
            r#"
module Top (word: output logic<16>) {{
    function outputs(first: output logic<8>, second: output logic<8>) {{
        first = 8'h12;
        second = 8'h34;
    }}
    always_comb {{
        word = 0;
        outputs({actuals});
    }}
}}
"#
        );
        assert_eq!(alias_warnings(&code).len(), expected, "{actuals}");
    }
}

#[test]
fn static_array_aliases_warn_and_dynamic_indices_are_not_assumed_to_alias() {
    for (actuals, expected) in [
        ("words[0], words[0]", 1),
        ("words[0], words[1]", 0),
        ("words[index], words[0]", 0),
    ] {
        let code = format!(
            r#"
module Top (index: input logic, words: output logic<8>[2]) {{
    function outputs(first: output logic<8>, second: output logic<8>) {{
        first = 8'h12;
        second = 8'h34;
    }}
    always_comb {{
        words = '{{0, 0}};
        outputs({actuals});
    }}
}}
"#
        );
        assert_eq!(alias_warnings(&code).len(), expected, "{actuals}");
    }
}

#[test]
fn input_output_aliasing_does_not_warn_about_copyout_order() {
    assert!(
        alias_warnings(
            r#"
module Top (d: input logic<8>, q: output logic<8>) {
    function update(value: input logic<8>, result: output logic<8>) {
        result = value + 8'd1;
    }
    always_comb {
        q = d;
        update(q, q);
    }
}
"#,
        )
        .is_empty()
    );
}

#[test]
fn repeated_instantiations_report_each_source_call_once() {
    let warnings = alias_warnings(
        r#"
module Child (d: input logic<8>, q: output logic<8>) {
    function outputs(first: output logic<8>, second: output logic<8>) {
        first = 1;
        second = 2;
    }
    function outer(x: input logic<8>, result: output logic<8>) {
        result = x;
        outputs(result, result);
    }
    always_comb { outer(d, q); }
}
module Top (d: input logic<8>, q: output logic<8>, r: output logic<8>) {
    inst a: Child (d, q);
    inst b: Child (d, q: r);
}
"#,
    );
    assert_eq!(warnings.len(), 1, "{warnings:?}");
}