gsim 1.1.4

High speed digital logic simulation
Documentation
use super::super::*;
use crate::import::yosys::*;
use crate::import::*;

#[cfg(test)]
fn test_yosys_import(
    json: &str,
    expected_inputs: &[(&str, NonZeroU8)],
    expected_outputs: &[(&str, NonZeroU8)],
) -> (ModuleConnections, Simulator) {
    let importer = YosysModuleImporter::from_json_str(json).unwrap();
    let mut builder = SimulatorBuilder::default();
    let connections = builder.import_module(&importer).unwrap();

    for &(port_name, port_width) in expected_inputs {
        let wire = *connections
            .inputs
            .get(port_name)
            .expect(&format!("expected input port `{port_name}` to be present"));
        let wire_width = builder.get_wire_width(wire).unwrap();
        assert_eq!(wire_width, port_width, "input port `{port_name}` has incorrect width;  expected: {port_width}  actual: {wire_width}");
    }

    for &(port_name, port_width) in expected_outputs {
        let wire = *connections
            .outputs
            .get(port_name)
            .expect(&format!("expected output port `{port_name}` to be present"));
        let wire_width = builder.get_wire_width(wire).unwrap();
        assert_eq!(wire_width, port_width, "output port `{port_name}` has incorrect width;  expected: {port_width}  actual: {wire_width}");
    }

    (connections, builder.build())
}

#[test]
fn simple_and_gate() {
    const JSON: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/import_tests/yosys/simple_and_gate.json"
    ));

    let width = NonZeroU8::new(8).unwrap();
    let (connections, mut sim) =
        test_yosys_import(JSON, &[("a", width), ("b", width)], &[("o", width)]);

    const TEST_DATA: &[BinaryGateTestData] = binary_gate_test_data!(
        (HIGH_Z, HIGH_Z) -> UNDEFINED,
        (HIGH_Z, UNDEFINED) -> UNDEFINED,
        (UNDEFINED, HIGH_Z) -> UNDEFINED,
        (UNDEFINED, UNDEFINED) -> UNDEFINED,
        (HIGH_Z, LOGIC_0) -> LOGIC_0,
        (HIGH_Z, LOGIC_1) -> UNDEFINED,
        (UNDEFINED, LOGIC_0) -> LOGIC_0,
        (UNDEFINED, LOGIC_1) -> UNDEFINED,
        (LOGIC_0, HIGH_Z) -> LOGIC_0,
        (LOGIC_1, HIGH_Z) -> UNDEFINED,
        (LOGIC_0, UNDEFINED) -> LOGIC_0,
        (LOGIC_1, UNDEFINED) -> UNDEFINED,
        (LOGIC_0, LOGIC_0) -> LOGIC_0,
        (LOGIC_0, LOGIC_1) -> LOGIC_0,
        (LOGIC_1, LOGIC_0) -> LOGIC_0,
        (LOGIC_1, LOGIC_1) -> LOGIC_1,

        (0xAA, 0xAA) -> 0xAA,
        (0x55, 0x55) -> 0x55,
        (0xAA, 0x55) -> 0,
    );

    test_binary_module(
        &mut sim,
        connections.inputs["a"],
        connections.inputs["b"],
        connections.outputs["o"],
        width,
        TEST_DATA,
        10,
    );
}

#[test]
fn program_counter() {
    const JSON: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/import_tests/yosys/program_counter.json"
    ));

    let width = NonZeroU8::new(32).unwrap();
    let (connections, mut sim) = test_yosys_import(
        JSON,
        &[
            ("data_in", width),
            ("inc", width),
            ("load", NonZeroU8::MIN),
            ("enable", NonZeroU8::MIN),
            ("reset", NonZeroU8::MIN),
            ("clk", NonZeroU8::MIN),
        ],
        &[("pc_next", width), ("pc_value", width)],
    );

    struct TestData {
        data_in: u32,
        inc: u32,
        load: bool,
        enable: bool,
        reset: bool,
        clk: bool,
        pc_next: LogicState,
        pc_value: LogicState,
    }

    macro_rules! test_data {
        (@BIT +) => { true };
        (@BIT -) => { false };
        ($(($d:literal, $i:literal, LD $ld:tt, EN $en:tt, RST $rst:tt, CLK $clk:tt) -> ($n:tt, $v:tt)),* $(,)?) => {
            &[
                $(
                    TestData {
                        data_in: $d,
                        inc: $i,
                        load: test_data!(@BIT $ld),
                        enable: test_data!(@BIT $en),
                        reset: test_data!(@BIT $rst),
                        clk: test_data!(@BIT $clk),
                        pc_next: logic_state!($n),
                        pc_value: logic_state!($v),
                    },
                )*
            ]
        };
    }

    const TEST_DATA: &[TestData] = test_data!(
        (0, 0, LD-, EN-, RST-, CLK-) -> (0, 0),
        (0, 0, LD-, EN-, RST+, CLK-) -> (0, 0),
        (0, 0, LD-, EN-, RST+, CLK+) -> (0, 0),
        (0, 0, LD-, EN-, RST-, CLK-) -> (0, 0),

        (0, 1, LD-, EN-, RST-, CLK-) -> (0, 0),
        (0, 1, LD-, EN+, RST-, CLK-) -> (1, 0),
        (0, 1, LD-, EN+, RST-, CLK+) -> (2, 1),
        (0, 1, LD-, EN-, RST-, CLK-) -> (1, 1),

        (4, 0, LD-, EN-, RST-, CLK-) -> (1, 1),
        (4, 0, LD+, EN-, RST-, CLK-) -> (1, 1),
        (4, 0, LD+, EN-, RST-, CLK+) -> (1, 1),
        (4, 0, LD-, EN-, RST-, CLK-) -> (1, 1),
        (4, 2, LD+, EN+, RST-, CLK-) -> (4, 1),
        (4, 2, LD+, EN+, RST-, CLK+) -> (4, 4),
        (4, 2, LD-, EN+, RST-, CLK-) -> (6, 4),
        (4, 2, LD-, EN-, RST-, CLK-) -> (4, 4),

        (0, 1, LD-, EN+, RST-, CLK-) -> (5, 4),
        (0, 1, LD-, EN+, RST+, CLK-) -> (0, 4),
        (0, 1, LD-, EN+, RST+, CLK+) -> (0, 0),
        (0, 1, LD-, EN+, RST-, CLK-) -> (1, 0),
    );

    for (i, test_data) in TEST_DATA.iter().enumerate() {
        sim.set_wire_drive(
            connections.inputs["data_in"],
            &LogicState::from_int(test_data.data_in),
        )
        .unwrap();
        sim.set_wire_drive(
            connections.inputs["inc"],
            &LogicState::from_int(test_data.inc),
        )
        .unwrap();
        sim.set_wire_drive(
            connections.inputs["load"],
            &LogicState::from_bool(test_data.load),
        )
        .unwrap();
        sim.set_wire_drive(
            connections.inputs["enable"],
            &LogicState::from_bool(test_data.enable),
        )
        .unwrap();
        sim.set_wire_drive(
            connections.inputs["reset"],
            &LogicState::from_bool(test_data.reset),
        )
        .unwrap();
        sim.set_wire_drive(
            connections.inputs["clk"],
            &LogicState::from_bool(test_data.clk),
        )
        .unwrap();

        match sim.run_sim(50) {
            SimulationRunResult::Ok => {}
            SimulationRunResult::MaxStepsReached => panic!("[TEST {i}] exceeded max steps"),
            SimulationRunResult::Err(err) => panic!("[TEST {i}] {err:?}"),
        }

        let pc_next = sim.get_wire_state(connections.outputs["pc_next"]).unwrap();
        let pc_value = sim.get_wire_state(connections.outputs["pc_value"]).unwrap();

        assert!(
            pc_next.eq(&test_data.pc_next, width),
            "[TEST {i}]  expected: {}  actual: {}",
            test_data.pc_next.display_string(width),
            pc_next.display_string(width),
        );

        assert!(
            pc_value.eq(&test_data.pc_value, width),
            "[TEST {i}]  expected: {}  actual: {}",
            test_data.pc_value.display_string(width),
            pc_value.display_string(width),
        );
    }
}

#[test]
fn proc_mux() {
    const JSON: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/import_tests/yosys/proc_mux.json"
    ));

    let (connections, mut sim) = test_yosys_import(
        JSON,
        &[
            ("data_in", NonZeroU8::new(3).unwrap()),
            ("select_0", NonZeroU8::MIN),
            ("select_1", NonZeroU8::MIN),
        ],
        &[("data_out", NonZeroU8::MIN)],
    );

    struct TestData {
        data: LogicState,
        select: [LogicState; 2],
        output: LogicState,
    }

    macro_rules! test_data {
        ($(($d:tt, [$($s:tt),+ $(,)?]) -> $o:tt),* $(,)?) => {
            &[
                $(
                    TestData {
                        data: logic_state!($d),
                        select: [$(logic_state!($s)),+],
                        output: logic_state!($o),
                    },
                )*
            ]
        };
    }

    const TEST_DATA: &[TestData] = test_data!(
        (0b000, [0, 0]) -> 0,
        (0b000, [1, 0]) -> 0,
        (0b000, [0, 1]) -> 0,

        (0b001, [0, 0]) -> 0,
        (0b001, [1, 0]) -> 1,
        (0b001, [0, 1]) -> 0,

        (0b010, [0, 0]) -> 0,
        (0b010, [1, 0]) -> 0,
        (0b010, [0, 1]) -> 1,

        (0b011, [0, 0]) -> 0,
        (0b011, [1, 0]) -> 1,
        (0b011, [0, 1]) -> 1,

        (0b100, [0, 0]) -> 1,
        (0b100, [1, 0]) -> 0,
        (0b100, [0, 1]) -> 0,

        (0b101, [0, 0]) -> 1,
        (0b101, [1, 0]) -> 1,
        (0b101, [0, 1]) -> 0,

        (0b110, [0, 0]) -> 1,
        (0b110, [1, 0]) -> 0,
        (0b110, [0, 1]) -> 1,

        (0b111, [0, 0]) -> 1,
        (0b111, [1, 0]) -> 1,
        (0b111, [0, 1]) -> 1,
    );

    for (i, test_data) in TEST_DATA.iter().enumerate() {
        sim.set_wire_drive(connections.inputs["data_in"], &test_data.data)
            .unwrap();
        sim.set_wire_drive(connections.inputs["select_0"], &test_data.select[0])
            .unwrap();
        sim.set_wire_drive(connections.inputs["select_1"], &test_data.select[1])
            .unwrap();

        match sim.run_sim(4) {
            SimulationRunResult::Ok => {}
            SimulationRunResult::MaxStepsReached => panic!("[TEST {i}] exceeded max steps"),
            SimulationRunResult::Err(err) => panic!("[TEST {i}] {err:?}"),
        }

        let output = sim.get_wire_state(connections.outputs["data_out"]).unwrap();

        assert!(
            output.eq(&test_data.output, NonZeroU8::MIN),
            "[TEST {i}]  expected: {}  actual: {}",
            test_data.output.display_string(NonZeroU8::MIN),
            output.display_string(NonZeroU8::MIN),
        );
    }
}

#[test]
fn duplicate_net_ids() {
    const JSON: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/import_tests/yosys/duplicate_net_ids.json"
    ));

    let i_width = NonZeroU8::new(1).unwrap();
    let o_width = NonZeroU8::new(3).unwrap();
    let (connections, mut sim) = test_yosys_import(JSON, &[("i", i_width)], &[("o", o_width)]);

    const TEST_DATA: &[UnaryGateTestData] = unary_gate_test_data!(
        HIGH_Z -> HIGH_Z,
        UNDEFINED -> UNDEFINED,
        LOGIC_0 -> LOGIC_0,
        LOGIC_1 -> LOGIC_1,
    );

    for (i, test_data) in TEST_DATA.iter().enumerate() {
        sim.set_wire_drive(connections.inputs["i"], &test_data.input)
            .unwrap();

        match sim.run_sim(2) {
            SimulationRunResult::Ok => {}
            SimulationRunResult::MaxStepsReached => panic!("[TEST {i}] exceeded max steps"),
            SimulationRunResult::Err(err) => panic!("[TEST {i}] {err:?}"),
        }

        let output_state = sim.get_wire_state(connections.outputs["o"]).unwrap();

        assert!(
            output_state.eq(&test_data.output, o_width),
            "[TEST {i}]  expected: {}  actual: {}",
            test_data.output.display_string(o_width),
            output_state.display_string(o_width),
        );
    }
}

#[test]
fn constant_order() {
    const JSON: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/import_tests/yosys/constant_order.json"
    ));

    let i_width = NonZeroU8::new(1).unwrap();
    let o_width = NonZeroU8::new(3).unwrap();
    let (connections, mut sim) = test_yosys_import(JSON, &[("i", i_width)], &[("o", o_width)]);

    let test_data = unary_gate_test_data!(
        HIGH_Z -> {% 1, 0, X},
        UNDEFINED -> {% 1, 0, X},
        LOGIC_0 -> {% 1, 0, 0},
        LOGIC_1 -> {% 1, 0, 1},
    );

    for (i, test_data) in test_data.iter().enumerate() {
        sim.set_wire_drive(connections.inputs["i"], &test_data.input)
            .unwrap();

        match sim.run_sim(2) {
            SimulationRunResult::Ok => {}
            SimulationRunResult::MaxStepsReached => panic!("[TEST {i}] exceeded max steps"),
            SimulationRunResult::Err(err) => panic!("[TEST {i}] {err:?}"),
        }

        let output_state = sim.get_wire_state(connections.outputs["o"]).unwrap();

        assert!(
            output_state.eq(&test_data.output, o_width),
            "[TEST {i}]  expected: {}  actual: {}",
            test_data.output.display_string(o_width),
            output_state.display_string(o_width),
        );
    }
}