library vunit_lib;
context vunit_lib.vunit_context;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.decode_types.all;
use work.common.all;
use work.ppc_fx_insns.all;

library osvvm;
use osvvm.RandomPkg.all;

entity multiply_tb is
    generic (runner_cfg : string := runner_cfg_default);
end multiply_tb;

architecture behave of multiply_tb is
    signal clk              : std_ulogic;
    constant clk_period     : time := 10 ns;

    constant pipeline_depth : integer := 4;

    signal m1               : MultiplyInputType := MultiplyInputInit;
    signal m2               : MultiplyOutputType;

begin
    multiply_0: entity work.multiply
        generic map (PIPELINE_DEPTH => pipeline_depth)
        port map (clk => clk, m_in => m1, m_out => m2);

    clk_process: process
    begin
        clk <= '0';
        wait for clk_period/2;
        clk <= '1';
        wait for clk_period/2;
    end process;

    stim_process: process
        variable ra, rb, rt, behave_rt: std_ulogic_vector(63 downto 0);
        variable si: std_ulogic_vector(15 downto 0);
        variable rnd : RandomPType;
    begin
        rnd.InitSeed(stim_process'path_name);

        test_runner_setup(runner, runner_cfg);

        while test_suite loop
            if run("Test interface") then
                wait for clk_period;

                m1.valid <= '1';
                m1.data1 <= x"0000000000001000";
                m1.data2 <= x"0000000000001111";

                wait for clk_period;
                check_false(?? m2.valid, result("for valid"));

                m1.valid <= '0';

                wait for clk_period;
                check_false(?? m2.valid, result("for valid"));

                wait for clk_period;
                check_false(?? m2.valid, result("for valid"));

                wait for clk_period;
                check_true(?? m2.valid, result("for valid"));
                check_equal(m2.result, 16#1111000#);

                wait for clk_period;
                check_false(?? m2.valid, result("for valid"));

                m1.valid <= '1';

                wait for clk_period;
                check_false(?? m2.valid, result("for valid"));

                m1.valid <= '0';

                wait for clk_period * (pipeline_depth-1);
                check_true(?? m2.valid, result("for valid"));
                check_equal(m2.result, 16#1111000#);

            elsif run("Test mulld") then
                mulld_loop : for i in 0 to 1000 loop
                    ra := rnd.RandSlv(ra'length);
                    rb := rnd.RandSlv(rb'length);

                    behave_rt := ppc_mulld(ra, rb);

                    m1.data1 <= ra;
                    m1.data2 <= rb;
                    m1.is_signed <= '1';
                    m1.subtract <= '0';
                    m1.addend <= (others => '0');
                    m1.valid <= '1';

                    wait for clk_period;

                    m1.valid <= '0';

                    wait for clk_period * (pipeline_depth-1);

                    check_true(?? m2.valid, result("for valid"));
                    check_equal(m2.result(63 downto 0), behave_rt, result("for mulld " & to_hstring(behave_rt)));
                end loop;

            elsif run("Test mulhdu") then
                mulhdu_loop : for i in 0 to 1000 loop
                    ra := rnd.RandSlv(ra'length);
                    rb := rnd.RandSlv(rb'length);

                    behave_rt := ppc_mulhdu(ra, rb);

                    m1.data1 <= ra;
                    m1.data2 <= rb;
                    m1.is_signed <= '0';
                    m1.subtract <= '0';
                    m1.addend <= (others => '0');
                    m1.valid <= '1';

                    wait for clk_period;

                    m1.valid <= '0';

                    wait for clk_period * (pipeline_depth-1);

                    check_true(?? m2.valid, result("for valid"));
                    check_equal(m2.result(127 downto 64), behave_rt, result("for mulhdu " & to_hstring(behave_rt)));
                end loop;

            elsif run("Test mulhd") then
                mulhd_loop : for i in 0 to 1000 loop
                    ra := rnd.RandSlv(ra'length);
                    rb := rnd.RandSlv(rb'length);

                    behave_rt := ppc_mulhd(ra, rb);

                    m1.data1 <= ra;
                    m1.data2 <= rb;
                    m1.is_signed <= '1';
                    m1.subtract <= '0';
                    m1.addend <= (others => '0');
                    m1.valid <= '1';

                    wait for clk_period;

                    m1.valid <= '0';

                    wait for clk_period * (pipeline_depth-1);

                    check_true(?? m2.valid, result("for valid"));
                    check_equal(m2.result(127 downto 64), behave_rt, result("for mulhd " & to_hstring(behave_rt)));
                end loop;

            elsif run("Test mullw") then
                mullw_loop : for i in 0 to 1000 loop
                    ra := rnd.RandSlv(ra'length);
                    rb := rnd.RandSlv(rb'length);

                    behave_rt := ppc_mullw(ra, rb);

                    m1.data1 <= (others => ra(31));
                    m1.data1(31 downto 0) <= ra(31 downto 0);
                    m1.data2 <= (others => rb(31));
                    m1.data2(31 downto 0) <= rb(31 downto 0);
                    m1.is_signed <= '1';
                    m1.subtract <= '0';
                    m1.addend <= (others => '0');
                    m1.valid <= '1';

                    wait for clk_period;

                    m1.valid <= '0';

                    wait for clk_period * (pipeline_depth-1);

                    check_true(?? m2.valid, result("for valid"));
                    check_equal(m2.result(63 downto 0), behave_rt, result("for mullw " & to_hstring(behave_rt)));
                end loop;

            elsif run("Test mulhw") then
                mulhw_loop : for i in 0 to 1000 loop
                    ra := rnd.RandSlv(ra'length);
                    rb := rnd.RandSlv(rb'length);

                    behave_rt := ppc_mulhw(ra, rb);

                    m1.data1 <= (others => ra(31));
                    m1.data1(31 downto 0) <= ra(31 downto 0);
                    m1.data2 <= (others => rb(31));
                    m1.data2(31 downto 0) <= rb(31 downto 0);
                    m1.is_signed <= '1';
                    m1.subtract <= '0';
                    m1.addend <= (others => '0');
                    m1.valid <= '1';

                    wait for clk_period;

                    m1.valid <= '0';

                    wait for clk_period * (pipeline_depth-1);

                    check_true(?? m2.valid, result("for valid"));
                    check_equal(m2.result(63 downto 32) & m2.result(63 downto 32), behave_rt, result("for mulhw " & to_hstring(behave_rt)));
                end loop;

            elsif run("Test mulhwu") then
                mulhwu_loop : for i in 0 to 1000 loop
                    ra := rnd.RandSlv(ra'length);
                    rb := rnd.RandSlv(rb'length);

                    behave_rt := ppc_mulhwu(ra, rb);

                    m1.data1 <= (others => '0');
                    m1.data1(31 downto 0) <= ra(31 downto 0);
                    m1.data2 <= (others => '0');
                    m1.data2(31 downto 0) <= rb(31 downto 0);
                    m1.is_signed <= '0';
                    m1.subtract <= '0';
                    m1.addend <= (others => '0');
                    m1.valid <= '1';

                    wait for clk_period;

                    m1.valid <= '0';

                    wait for clk_period * (pipeline_depth-1);

                    check_true(?? m2.valid, result("for valid"));
                    check_equal(m2.result(63 downto 32) & m2.result(63 downto 32), behave_rt, result("for mulhwu " & to_hstring(behave_rt)));
                end loop;

            elsif run("Test mulli") then
                mulli_loop : for i in 0 to 1000 loop
                    ra := rnd.RandSlv(ra'length);
                    si := rnd.RandSlv(si'length);

                    behave_rt := ppc_mulli(ra, si);

                    m1.data1 <= ra;
                    m1.data2 <= (others => si(15));
                    m1.data2(15 downto 0) <= si;
                    m1.is_signed <= '1';
                    m1.subtract <= '0';
                    m1.addend <= (others => '0');
                    m1.valid <= '1';

                    wait for clk_period;

                    m1.valid <= '0';

                    wait for clk_period * (pipeline_depth-1);

                    check_true(?? m2.valid, result("for valid"));
                    check_equal(m2.result(63 downto 0), behave_rt, result("for mulli " & to_hstring(behave_rt)));
                end loop;
            end if;
        end loop;

        test_runner_cleanup(runner);
        wait;
    end process;
end behave;
