-- ============================================================
-- TESTBENCH TIMING - Progetto Reti Logiche 2025/2026
-- ============================================================
-- Verifica il numero di cicli di clock per ciascuna operazione.
--
-- Formule attese (calibrate sull'FSM con stato S_DONE, che aggiunge
-- 1 ciclo a fine operazione per garantire DONE dopo il commit in memoria):
--   OP=10 inserimento:  stati = 6 + 2*max(N,1) + 3k
--                       (N = task gia' in lista, scanditi dal controllo
--                        duplicati; k = task spostati per fare posto)
--   OP=01 rimozione:    stati = 6 + 3k  (k = task spostati, lista vuota = 2)
--   OP=00 decremento:   stati = 4 + 3n  (n = task in lista)
--
-- Per ogni test viene stampato:
--   - il numero di cicli misurati
--   - il numero di cicli attesi
--   - PASS o FAIL
-- ============================================================

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

entity project_tb_timing is
end project_tb_timing;

architecture timing_arch of project_tb_timing is

    constant CLOCK_PERIOD : time := 20 ns;

    signal tb_clk           : std_logic := '0';
    signal tb_rst           : std_logic := '0';
    signal tb_start         : std_logic := '0';
    signal tb_done          : std_logic;
    signal tb_o_task_id     : std_logic_vector(5 downto 0);
    signal tb_task_priority : std_logic_vector(1 downto 0) := "00";
    signal tb_op            : std_logic_vector(1 downto 0) := "00";
    signal tb_i_task_id     : std_logic_vector(5 downto 0) := "000000";

    signal exc_o_mem_addr : std_logic_vector(15 downto 0);
    signal exc_o_mem_data : std_logic_vector(7 downto 0);
    signal exc_o_mem_we   : std_logic;
    signal exc_o_mem_en   : std_logic;

    signal init_o_mem_addr : std_logic_vector(15 downto 0) := (others => '0');
    signal init_o_mem_data : std_logic_vector(7 downto 0)  := (others => '0');
    signal init_o_mem_we   : std_logic := '0';
    signal init_o_mem_en   : std_logic := '0';

    signal tb_o_mem_addr : std_logic_vector(15 downto 0);
    signal tb_o_mem_data : std_logic_vector(7 downto 0);
    signal tb_o_mem_we   : std_logic;
    signal tb_o_mem_en   : std_logic;
    signal tb_i_mem_data : std_logic_vector(7 downto 0);

    signal memory_control : std_logic := '0';

    type ram_type is array (65535 downto 0) of std_logic_vector(7 downto 0);
    signal RAM : ram_type := (others => "00000000");

    component project_reti_logiche is
        port (
            i_clk           : in  std_logic;
            i_rst           : in  std_logic;
            i_start         : in  std_logic;
            i_task_id       : in  std_logic_vector(5 downto 0);
            i_task_priority : in  std_logic_vector(1 downto 0);
            i_op            : in  std_logic_vector(1 downto 0);
            o_done          : out std_logic;
            o_task_id       : out std_logic_vector(5 downto 0);
            o_mem_addr      : out std_logic_vector(15 downto 0);
            i_mem_data      : in  std_logic_vector(7 downto 0);
            o_mem_data      : out std_logic_vector(7 downto 0);
            o_mem_we        : out std_logic;
            o_mem_en        : out std_logic
        );
    end component;

begin

    UUT : project_reti_logiche
        port map (
            i_clk           => tb_clk,
            i_rst           => tb_rst,
            i_start         => tb_start,
            i_task_id       => tb_i_task_id,
            i_task_priority => tb_task_priority,
            i_op            => tb_op,
            o_done          => tb_done,
            o_task_id       => tb_o_task_id,
            o_mem_addr      => exc_o_mem_addr,
            i_mem_data      => tb_i_mem_data,
            o_mem_data      => exc_o_mem_data,
            o_mem_we        => exc_o_mem_we,
            o_mem_en        => exc_o_mem_en
        );

    tb_clk <= not tb_clk after CLOCK_PERIOD / 2;

    MEM : process (tb_clk)
    begin
        if tb_clk'event and tb_clk = '1' then
            if tb_o_mem_en = '1' then
                if tb_o_mem_we = '1' then
                    RAM(to_integer(unsigned(tb_o_mem_addr))) <= tb_o_mem_data after 1 ns;
                    tb_i_mem_data <= tb_o_mem_data after 1 ns;
                else
                    tb_i_mem_data <= RAM(to_integer(unsigned(tb_o_mem_addr))) after 1 ns;
                end if;
            end if;
        end if;
    end process;

    memory_signal_swapper : process (memory_control,
                                     init_o_mem_addr, init_o_mem_data, init_o_mem_en, init_o_mem_we,
                                     exc_o_mem_addr,  exc_o_mem_data,  exc_o_mem_en,  exc_o_mem_we)
    begin
        tb_o_mem_addr <= init_o_mem_addr;
        tb_o_mem_data <= init_o_mem_data;
        tb_o_mem_en   <= init_o_mem_en;
        tb_o_mem_we   <= init_o_mem_we;
        if memory_control = '1' then
            tb_o_mem_addr <= exc_o_mem_addr;
            tb_o_mem_data <= exc_o_mem_data;
            tb_o_mem_en   <= exc_o_mem_en;
            tb_o_mem_we   <= exc_o_mem_we;
        end if;
    end process;

    -- ============================================================
    -- Processo principale
    -- ============================================================
    main : process

        -- Cicli misurati e attesi
        variable t_start  : time;
        variable cycles   : integer;
        variable expected : integer;

        -- --------------------------------------------------------
        -- Reset
        -- --------------------------------------------------------
        procedure do_reset is
        begin
            tb_start <= '0';
            tb_rst   <= '1';
            wait for 100 ns;
            tb_rst   <= '0';
            wait until tb_done = '0';
            wait until falling_edge(tb_clk);
        end procedure;

        -- --------------------------------------------------------
        -- Esegue un'operazione e misura i cicli dal primo fronte
        -- di clock dopo START=1 fino al fronte che porta DONE=1
        -- --------------------------------------------------------
        procedure run_and_measure (
            op       : std_logic_vector(1 downto 0);
            task_id  : std_logic_vector(5 downto 0);
            priority : std_logic_vector(1 downto 0);
            exp      : integer;
            test_num : integer
        ) is
        begin
            wait until falling_edge(tb_clk);
            tb_op            <= op;
            tb_i_task_id     <= task_id;
            tb_task_priority <= priority;
            tb_start         <= '1';

            -- Il primo fronte di clock dopo START campiona l'ingresso
            -- ed entra nel primo stato dell'operazione: inizia il conteggio
            wait until rising_edge(tb_clk);
            t_start := now;

            wait until rising_edge(tb_done);
            -- DONE viene registrato sul fronte: questo è l'ultimo stato
            cycles   := (now - t_start) / CLOCK_PERIOD;
            expected := exp;

            if cycles = expected then
                report "[PASS] test=" & integer'image(test_num)
                       & " | cicli misurati=" & integer'image(cycles)
                       & " attesi="           & integer'image(expected);
            else
                report "[FAIL] test=" & integer'image(test_num)
                       & " | cicli misurati=" & integer'image(cycles)
                       & " attesi="           & integer'image(expected)
                severity failure;
            end if;

            tb_start <= '0';
            wait until falling_edge(tb_done);
        end procedure;

        -- --------------------------------------------------------
        -- Inserisce un task senza misurarlo (solo per preparare la lista)
        -- --------------------------------------------------------
        procedure insert_silent (
            task_id  : std_logic_vector(5 downto 0);
            priority : std_logic_vector(1 downto 0)
        ) is
        begin
            wait until falling_edge(tb_clk);
            tb_op            <= "10";
            tb_i_task_id     <= task_id;
            tb_task_priority <= priority;
            tb_start         <= '1';
            wait until rising_edge(tb_done);
            tb_start <= '0';
            wait until falling_edge(tb_done);
        end procedure;

    begin
        memory_control <= '1';
        wait for 50 ns;

        -- ============================================================
        -- OP=10: Inserimento  ->  stati attesi = 6 + 4k
        -- ============================================================
        report "==============================";
        report "OP=10 Inserimento: 6 + 2*max(N,1) + 3k stati";
        report "==============================";

        -- N=0, k=0: lista vuota -> 6 + 2 + 0 = 8 stati
        -- OP=10 N=0 k=0 (lista vuota)
        do_reset;
        run_and_measure("10", "000001", "01", 8, 1);

        -- N=1, k=1: 1 task con priorità minore -> spostato -> 6 + 2 + 3 = 11
        -- OP=10 N=1 k=1 (1 task spostato)
        do_reset;
        insert_silent("000001", "10");
        run_and_measure("10", "000010", "01", 11, 2);

        -- N=2, k=2: 2 task da spostare -> 6 + 4 + 6 = 16
        -- OP=10 N=2 k=2 (2 task spostati)
        do_reset;
        insert_silent("000001", "10");
        insert_silent("000010", "10");
        run_and_measure("10", "000011", "01", 16, 3);

        -- N=3, k=3: 3 task da spostare -> 6 + 6 + 9 = 21
        -- OP=10 N=3 k=3 (3 task spostati)
        do_reset;
        insert_silent("000001", "10");
        insert_silent("000010", "10");
        insert_silent("000011", "10");
        run_and_measure("10", "000100", "01", 21, 4);

        -- N=2, k=0: inserimento in fondo, lista non vuota -> 6 + 4 + 0 = 10
        -- OP=10 N=2 k=0 (inserimento in fondo)
        do_reset;
        insert_silent("000001", "00");
        insert_silent("000010", "01");
        run_and_measure("10", "000011", "11", 10, 5);

        -- ============================================================
        -- OP=01: Rimozione  ->  stati attesi = 5 + 3k  (lista vuota = 1)
        -- ============================================================
        report "==============================";
        report "OP=01 Rimozione: 6 + 3k stati (lista vuota = 2)";
        report "==============================";

        -- Lista vuota -> 2 stati (CHECK_NUMBER + S_DONE)
        -- OP=01 lista vuota
        do_reset;
        run_and_measure("01", "000000", "00", 2, 6);

        -- k=0: 1 solo task, nessuno spostamento -> 6 stati
        -- OP=01 k=0 (1 task, nessuno spostamento)
        do_reset;
        insert_silent("000001", "01");
        run_and_measure("01", "000000", "00", 6, 7);

        -- k=1: 2 task, 1 da spostare -> 9 stati
        -- OP=01 k=1 (1 task spostato)
        do_reset;
        insert_silent("000001", "01");
        insert_silent("000010", "01");
        run_and_measure("01", "000000", "00", 9, 8);

        -- k=2: 3 task, 2 da spostare -> 12 stati
        -- OP=01 k=2 (2 task spostati)
        do_reset;
        insert_silent("000001", "01");
        insert_silent("000010", "01");
        insert_silent("000011", "01");
        run_and_measure("01", "000000", "00", 12, 9);

        -- k=3: 4 task, 3 da spostare -> 15 stati
        -- OP=01 k=3 (3 task spostati)
        do_reset;
        insert_silent("000001", "01");
        insert_silent("000010", "01");
        insert_silent("000011", "01");
        insert_silent("000100", "01");
        run_and_measure("01", "000000", "00", 15, 10);

        -- ============================================================
        -- OP=00: Decremento  ->  stati attesi = 3 + 3n
        -- ============================================================
        report "==============================";
        report "OP=00 Decremento: 4 + 3n stati";
        report "==============================";

        -- n=0: lista vuota -> 4 stati
        -- OP=00 n=0 (lista vuota)
        do_reset;
        run_and_measure("00", "000000", "00", 4, 11);

        -- n=1 -> 7 stati
        -- OP=00 n=1
        do_reset;
        insert_silent("000001", "01");
        run_and_measure("00", "000000", "00", 7, 12);

        -- n=2 -> 10 stati
        -- OP=00 n=2
        do_reset;
        insert_silent("000001", "01");
        insert_silent("000010", "10");
        run_and_measure("00", "000000", "00", 10, 13);

        -- n=3 -> 13 stati
        -- OP=00 n=3
        do_reset;
        insert_silent("000001", "01");
        insert_silent("000010", "10");
        insert_silent("000011", "11");
        run_and_measure("00", "000000", "00", 13, 14);

        -- n=4 con saturazione (tutti prio=3) -> 4 + 3*4 = 16 stati
        -- OP=00 n=4 (tutti gia prio=3, saturazione)
        do_reset;
        insert_silent("000001", "11");
        insert_silent("000010", "11");
        insert_silent("000011", "11");
        insert_silent("000100", "11");
        run_and_measure("00", "000000", "00", 16, 15);

        -- ============================================================
        -- Fine
        -- ============================================================
        assert false
            report "Tutti i test di timing sono PASSATI"
            severity failure;

    end process;

end architecture;