veryl-analyzer 0.22.0

A modern hardware description language
Documentation
use super::*;

#[test]
fn modport_connection_explicit_and_bare_outputs() {
    for connection in ["bus.mp", "bus", "mp"] {
        let code = format!(
            r#"
            interface Bus {{
                var data: logic;
                modport mp {{ data: output, }}
            }}
            module Writer (bus: modport Bus::mp) {{
                assign bus.data = 0;
            }}
            module Top {{
                inst {instance}: Bus;
                inst writer: Writer(bus: {connection});
            }}
            "#,
            instance = if connection == "mp" { "mp" } else { "bus" },
        );
        assert!(analyze(&code).is_empty(), "{connection}");
    }
}

#[test]
fn modport_connection_forwarded_port_named_like_modport() {
    let code = r#"
        interface Bus {
            var data: logic;
            modport mp { data: output, }
        }
        module Writer (bus: modport Bus::mp) {
            assign bus.data = 0;
        }
        module Forward (mp: modport Bus::mp) {
            inst writer: Writer(bus: mp);
        }
        module Top {
            inst bus: Bus;
            inst forward: Forward(mp: bus.mp);
        }
    "#;
    assert!(analyze(code).is_empty());
}

#[test]
fn modport_connection_preserves_unassigned_and_multiple_assignment_checks() {
    let code = r#"
        interface Bus {
            var data: logic;
            #[allow(unused_variable)]
            var undriven: logic;
            modport mp { data: output, }
        }
        module Writer (bus: modport Bus::mp) {
            assign bus.data = 0;
        }
        module Top {
            inst bus: Bus;
            inst writer: Writer(bus: bus.mp);
        }
    "#;
    let errors = analyze(code);
    assert_eq!(errors.len(), 1, "{errors:?}");
    assert!(
        matches!(&errors[0], AnalyzerError::UnassignVariable { identifier, .. } if identifier == "bus.undriven")
    );
    let errors = analyze(&code.replace("inst writer:", "assign bus.data = 1; inst writer:"));
    assert!(
        errors
            .iter()
            .any(|e| matches!(e, AnalyzerError::MultipleAssignment { .. })),
        "{errors:?}"
    );
}

#[test]
fn modport_connection_explicit_input_preserves_feedback() {
    let code = r#"
        interface Bus {
            var request: logic;
            var response: logic;
            modport mp { request: input, response: output, }
        }
        module Target (bus: modport Bus::mp) {
            assign bus.response = bus.request;
        }
        module Top {
            inst bus: Bus;
            inst target: Target(bus: bus.mp);
            assign bus.request = bus.response;
        }
    "#;
    let errors = analyze(code);
    assert!(
        errors
            .iter()
            .any(|e| matches!(e, AnalyzerError::CombinationalLoop { .. })),
        "{errors:?}"
    );
    assert!(
        !errors
            .iter()
            .any(|e| matches!(e, AnalyzerError::UnassignVariable { .. })),
        "{errors:?}"
    );
    assert!(
        analyze(&code.replace(
            "assign bus.request = bus.response;",
            "assign bus.request = 0;"
        ))
        .is_empty()
    );
}

#[test]
fn modport_connection_indexed_interface_preserves_selection() {
    let code = r#"
        interface Bus {
            var data: logic;
            modport mp { data: output, }
        }
        module Writer (bus: modport Bus::mp) {
            assign bus.data = 0;
        }
        module Top {
            inst bus: Bus[2];
            inst first: Writer(bus: bus[0].mp);
            inst second: Writer(bus: bus[1].mp);
        }
    "#;
    assert!(analyze(code).is_empty());
    let errors = analyze(&code.replace("inst second: Writer(bus: bus[1].mp);", ""));
    assert_eq!(errors.len(), 1, "{errors:?}");
    assert!(
        matches!(&errors[0], AnalyzerError::UnassignVariable { identifier, .. } if identifier == "bus.data[32'h00000001]")
    );
}

#[test]
fn modport_connection_unknown_interface_array_size() {
    let code = r#"
        package p {
            const N: u32 = $sv::foo_pkg::BAR;
            enum cmd_e { A, B, }
            struct payload_t { data: logic<8>, }
        }
        interface my_if {
            var cmd: p::cmd_e;
            var payload: p::payload_t;
            var data: logic<8>;
            modport master { cmd: output, payload: output, data: output, }
        }
        module leaf (m_if: modport my_if::master,) {
            always_comb {
                m_if.cmd = p::cmd_e::A;
                m_if.payload = p::payload_t'{data: 0};
                m_if.data = 0;
            }
        }
        module top {
            inst ifs: my_if [p::N + 1];
            inst u: leaf (m_if: ifs[p::N].master,);
        }
    "#;
    assert!(analyze(code).is_empty());
}

#[test]
fn modport_connection_imported_function_preserves_capture_binding() {
    let code = r#"
        interface Bus {
            var data: logic;
            function read () -> logic { return data; }
            modport mp { read: import, }
        }
        module Reader (bus: modport Bus::mp, o: output logic) {
            assign o = bus.read();
        }
        module Top {
            inst bus: Bus;
            var value: logic;
            inst reader: Reader(bus: bus.mp, o: value);
            assign bus.data = value;
        }
    "#;
    let errors = analyze(code);
    assert!(
        errors
            .iter()
            .any(|e| matches!(e, AnalyzerError::CombinationalLoop { .. })),
        "{errors:?}"
    );
    assert!(analyze(&code.replace("assign bus.data = value;", "assign bus.data = 0;")).is_empty());
}