-- Progetto di reti logiche 2025/2026 - Aleandro Pagani

-- Scheduler di task con priorità. Gestisce una lista ordinata in memoria
-- esterna (mem[0]=contatore, mem[1..N]=task come ID&PRIORITY a 8 bit).
-- OP 00=invecchiamento, 01=rimozione, 10=inserimento, 11=svuotamento.
-- FSM a due processi (combinatorio + sincrono), reset asincrono.

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


entity 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 project_reti_logiche;


architecture FSM of project_reti_logiche is

    ----------------------------------------------------------------
    -- Stati della FSM (prefisso S_xx_ = stati dell'operazione "xx")
    ----------------------------------------------------------------
    type state_type is (
        -- Gestione del protocollo
        S_IDLE,                 -- attesa di START, dispatch delle operazioni
        S_RESET,                -- reset: scrive 0 in mem[0]
        S_DONE,                 -- DONE = 1 finché START non torna a 0

        -- OP = "00": invecchiamento
        S_00_READ,              -- avvia la lettura del primo task (mem[1])
        S_00_WAIT,              -- attesa latenza di lettura
        S_00_CHECK,             -- fine lista? altrimenti incrementa/satura
        S_00_GO_NEXT,           -- avvia la lettura del task successivo

        -- OP = "01": rimozione
        S_01_CHECK_NUMBER,      -- lista vuota? altrimenti avvia lettura di mem[1]
        S_01_WAIT,              -- attesa latenza di lettura
        S_01_WRITE,             -- salva l'ID estratto, avvia lettura di mem[2]
        S_01_CHECK_END,         -- fine lista? scrive il contatore decrementato
        S_01_COPY,              -- copia il task letto una posizione più su
        S_01_GO_NEXT,           -- avvia la lettura del task successivo
        S_01_WAIT_FOR_COUNT,    -- attesa della scrittura nel contatore

        -- OP = "10": inserimento
        S_10_PLACE_AT_START,    -- scarta ID = 0, avvia la scansione duplicati
        S_10_WAIT_FOR_CHECK,    -- attesa latenza di lettura (fase scansione)
        S_10_CHECK_ID,          -- duplicato? prosegue scansione o passa all'inserimento
        S_10_WAIT,              -- attesa latenza di lettura (fase inserimento)
        S_10_COMPARE,           -- inserisce qui oppure sposta il task in basso
        S_10_GO_NEXT,           -- avvia la lettura del task precedente
        S_10_UPDATE_COUNT,      -- scrive il contatore incrementato
        S_10_WAIT_FOR_COUNT,    -- attesa della scrittura nel contatore

        -- OP = "11": svuotamento
        S_11_UPDATE_COUNT,      -- scrive 0 in mem[0]
        S_11_WAIT_FOR_COUNT     -- attesa del commit della scrittura del contatore
    );

    ----------------------------------------------------------------
    -- Segnali interni (coppie current/next)
    ----------------------------------------------------------------
    signal state              : state_type;
    signal next_state         : state_type;

    -- Indirizzo di memoria su cui la FSM sta lavorando (0 = contatore)
    signal current_mem_addr   : std_logic_vector(15 downto 0);
    signal next_mem_addr      : std_logic_vector(15 downto 0);

    -- Copia locale del numero di task presenti in lista (mem[0])
    signal current_task_count : std_logic_vector(7 downto 0);
    signal next_task_count    : std_logic_vector(7 downto 0);

    -- ID estratto dall'ultima rimozione, presentato su o_task_id in S_DONE
    signal current_popped_id  : std_logic_vector(5 downto 0);
    signal next_popped_id     : std_logic_vector(5 downto 0);

    ----------------------------------------------------------------
    -- Segnali di controllo calcolati dal processo combinatorio,
    -- registrati sulle uscite (o_done, o_task_id, interfaccia memoria)
    ----------------------------------------------------------------
    signal ctrl_done     : std_logic;
    signal ctrl_mem_en   : std_logic;
    signal ctrl_mem_we   : std_logic;
    signal ctrl_mem_data : std_logic_vector(7 downto 0);
    signal ctrl_task_id  : std_logic_vector(5 downto 0);

begin

    ----------------------------------------------------------------
    -- PROCESSO COMBINATORIO
    -- Calcola stato prossimo e segnali di controllo.
    -- Le assegnazioni di default in testa coprono ogni ramo:
    -- nessun latch inferito.
    ----------------------------------------------------------------
    process (state, i_start, i_op, i_mem_data, i_task_id, i_task_priority,
             current_task_count, current_mem_addr, current_popped_id)
    begin
        next_state      <= state;
        next_task_count <= current_task_count;
        next_mem_addr   <= current_mem_addr;
        next_popped_id  <= current_popped_id;
        ctrl_done       <= '0';
        ctrl_mem_en     <= '0';
        ctrl_mem_we     <= '0';
        ctrl_mem_data   <= (others => '0');
        ctrl_task_id    <= (others => '0');

        case state is

            ------------------------------------------------------------
            -- S_IDLE: attesa di START. Al dispatch azzera l'ID estratto,
            -- così o_task_id varrà 0 in S_DONE per ogni operazione che
            -- non sia una rimozione andata a buon fine.
            ------------------------------------------------------------
            when S_IDLE =>
                if i_start = '1' then
                    next_popped_id <= (others => '0');
                    case i_op is
                        when "00"   => next_state <= S_00_READ;            -- invecchiamento
                        when "01"   => next_state <= S_01_CHECK_NUMBER;    -- rimozione
                        when "10"   => next_state <= S_10_PLACE_AT_START;  -- inserimento
                        when "11"   => next_state <= S_11_UPDATE_COUNT;    -- svuotamento
                        when others => null;
                    end case;
                end if;

            ------------------------------------------------------------
            -- S_RESET: dopo un reset la lista deve risultare vuota.
            -- Scrive 0 in mem[0] tenendo DONE a 1.
            ------------------------------------------------------------
            when S_RESET =>
                next_state      <= S_DONE;
                next_task_count <= (others => '0');
                next_mem_addr   <= (others => '0');
                next_popped_id  <= (others => '0');
                ctrl_done       <= '1';
                ctrl_mem_en     <= '1';
                ctrl_mem_we     <= '1';
                ctrl_mem_data   <= (others => '0');

            ------------------------------------------------------------
            -- S_DONE: alza DONE e lo tiene a 1 finché START non torna a 0,
            -- come richiesto dal protocollo di hand-shake.
            -- DONE viene asserito qui, un ciclo dopo lo stato terminale
            -- dell'operazione: sale quindi sempre dopo il commit in
            -- memoria dell'ultima scrittura, e chi campiona la memoria
            -- sul fronte di DONE legge dati già aggiornati.
            -- o_task_id è valido per tutta la finestra DONE = 1.
            ------------------------------------------------------------
            when S_DONE =>
                if i_start = '1' then
                    ctrl_done    <= '1';
                    ctrl_task_id <= current_popped_id;
                else
                    next_state   <= S_IDLE;
                end if;

            ------------------------------------------------------------
            -- OP = "00" (invecchiamento)
            -- Scorre la lista da mem[1] a mem[N]. Per ogni task con
            -- priorità < 3 riscrive l'intero byte incrementato di 1:
            -- equivale a incrementare i soli 2 bit di priorità, perché
            -- la scrittura avviene solo se la priorità non è "11" e il
            -- riporto non può quindi mai propagarsi nei bit dell'ID.
            -- I task già a priorità 3 non vengono riscritti (saturazione).
            ------------------------------------------------------------
            when S_00_READ =>
                next_state    <= S_00_WAIT;
                next_mem_addr <= x"0001";
                ctrl_mem_en   <= '1';

            when S_00_WAIT =>
                next_state <= S_00_CHECK;

            when S_00_CHECK =>
                if unsigned(current_mem_addr) = resize(unsigned(current_task_count), 16) + 1 then
                    next_state    <= S_DONE;
                elsif i_mem_data(1 downto 0) /= "11" then
                    next_state    <= S_00_GO_NEXT;
                    ctrl_mem_en   <= '1';
                    ctrl_mem_we   <= '1';
                    ctrl_mem_data <= std_logic_vector(unsigned(i_mem_data) + 1);
                else
                    next_state    <= S_00_GO_NEXT;
                end if;

            when S_00_GO_NEXT =>
                next_state    <= S_00_WAIT;
                next_mem_addr <= std_logic_vector(unsigned(current_mem_addr) + 1);
                ctrl_mem_en   <= '1';

            ------------------------------------------------------------
            -- OP = "01" (rimozione)
            -- Se la lista è vuota termina subito (o_task_id resterà 0).
            -- Altrimenti salva l'ID di mem[1], compatta la lista
            -- copiando ogni task una posizione più in alto
            -- (mem[j] -> mem[j-1] per j = 2...N) e infine scrive il
            -- contatore decrementato in mem[0].
            ------------------------------------------------------------
            when S_01_CHECK_NUMBER =>
                if current_task_count = x"00" then
                    next_state    <= S_DONE;
                else
                    next_state    <= S_01_WAIT;
                    next_mem_addr <= x"0001";
                    ctrl_mem_en   <= '1';
                end if;

            when S_01_WAIT =>
                next_state <= S_01_WRITE;

            -- Salva l'ID del task estratto e avvia la lettura di mem[2].
            when S_01_WRITE =>
                next_state     <= S_01_CHECK_END;
                next_mem_addr  <= std_logic_vector(unsigned(current_mem_addr) + 1);
                ctrl_mem_en    <= '1';
                next_popped_id <= i_mem_data(7 downto 2);

            -- Qui current_mem_addr vale j (lettura di mem[j] in corso).
            -- Se j = N + 1 la compattazione è finita: scrive N - 1 in mem[0].
            when S_01_CHECK_END =>
                if unsigned(current_mem_addr) = resize(unsigned(current_task_count), 16) + 1 then
                    next_state      <= S_01_WAIT_FOR_COUNT;
                    next_task_count <= std_logic_vector(unsigned(current_task_count) - 1);
                    next_mem_addr   <= x"0000";
                    ctrl_mem_en     <= '1';
                    ctrl_mem_we     <= '1';
                    ctrl_mem_data   <= std_logic_vector(unsigned(current_task_count) - 1);
                else
                    next_state      <= S_01_COPY;
                end if;

            -- Scrive mem[j], appena letto, in mem[j-1].
            when S_01_COPY =>
                next_state    <= S_01_GO_NEXT;
                next_mem_addr <= std_logic_vector(unsigned(current_mem_addr) - 1);
                ctrl_mem_en   <= '1';
                ctrl_mem_we   <= '1';
                ctrl_mem_data <= i_mem_data;

            -- In S_01_COPY l'indirizzo è sceso a j-1 per la scrittura:
            -- il +2 riporta la lettura su j+1, il task successivo.
            when S_01_GO_NEXT =>
                next_state    <= S_01_CHECK_END;
                next_mem_addr <= std_logic_vector(unsigned(current_mem_addr) + 2);
                ctrl_mem_en   <= '1';

            -- Un ciclo di attesa: al suo termine la scrittura del
            -- contatore è stata campionata dalla memoria.
            when S_01_WAIT_FOR_COUNT =>
                next_state <= S_DONE;

            ------------------------------------------------------------
            -- OP = "10" (inserimento) - due fasi:
            --  1) scansione duplicati: legge mem[N]...mem[1]; se trova un
            --     task con lo stesso ID termina senza modifiche;
            --  2) inserimento dal fondo: confronta il nuovo task con
            --     mem[j] per j = N...1; finché il nuovo task ha priorità
            --     migliore (valore minore) sposta mem[j] in mem[j+1],
            --     altrimenti scrive il nuovo task in mem[j+1].
            --     Il confronto ">=" colloca il nuovo task DOPO quelli di
            --     pari priorità.
            ------------------------------------------------------------
            -- Scarta ID = 0.
            -- Con lista vuota (N = 0) la scansione legge mem[0], cioè il
            -- contatore: il confronto in S_10_CHECK_ID non può dare un
            -- falso duplicato, perché in quel caso il contatore vale 0 e
            -- l'ID, già filtrato, non è mai 0.
            when S_10_PLACE_AT_START =>
                if i_task_id = "000000" then
                    next_state    <= S_DONE;
                else
                    next_state    <= S_10_WAIT_FOR_CHECK;
                    next_mem_addr <= x"00" & current_task_count;
                    ctrl_mem_en   <= '1';
                end if;

            when S_10_WAIT_FOR_CHECK =>
                next_state <= S_10_CHECK_ID;

            when S_10_CHECK_ID =>
                if i_mem_data(7 downto 2) = i_task_id then
                    -- ID già presente: l'inserimento viene ignorato
                    next_state    <= S_DONE;
                elsif unsigned(current_mem_addr) <= 1 then
                    -- scansione completata: rilegge mem[N] e passa
                    -- alla fase di inserimento
                    next_state    <= S_10_WAIT;
                    next_mem_addr <= x"00" & current_task_count;
                    ctrl_mem_en   <= '1';
                else
                    -- prosegue la scansione verso l'inizio della lista
                    next_state    <= S_10_WAIT_FOR_CHECK;
                    next_mem_addr <= std_logic_vector(unsigned(current_mem_addr) - 1);
                    ctrl_mem_en   <= '1';
                end if;

            when S_10_WAIT =>
                next_state <= S_10_COMPARE;

            -- Qui current_mem_addr vale j (mem[j] appena letto).
            -- current_mem_addr = 0 significa che tutta la lista è stata spostata:
            -- il nuovo task va in testa (mem[1]).
            when S_10_COMPARE =>
                if (current_mem_addr = x"0000") or
                   (unsigned(i_task_priority) >= unsigned(i_mem_data(1 downto 0))) then
                    -- posizione trovata: scrive il nuovo task in mem[j+1]
                    next_state    <= S_10_UPDATE_COUNT;
                    next_mem_addr <= std_logic_vector(unsigned(current_mem_addr) + 1);
                    ctrl_mem_en   <= '1';
                    ctrl_mem_we   <= '1';
                    ctrl_mem_data <= i_task_id & i_task_priority;
                else
                    -- il nuovo task deve stare più in alto: sposta
                    -- mem[j] in mem[j+1] e prosegue verso l'alto
                    next_state    <= S_10_GO_NEXT;
                    next_mem_addr <= std_logic_vector(unsigned(current_mem_addr) + 1);
                    ctrl_mem_en   <= '1';
                    ctrl_mem_we   <= '1';
                    ctrl_mem_data <= i_mem_data;
                end if;

            -- In S_10_COMPARE l'indirizzo è salito a j+1 per la
            -- scrittura: il -2 riporta la lettura su j-1, il task
            -- precedente da confrontare.
            when S_10_GO_NEXT =>
                next_state    <= S_10_WAIT;
                next_mem_addr <= std_logic_vector(unsigned(current_mem_addr) - 2);
                ctrl_mem_en   <= '1';

            when S_10_UPDATE_COUNT =>
                next_state      <= S_10_WAIT_FOR_COUNT;
                next_task_count <= std_logic_vector(unsigned(current_task_count) + 1);
                next_mem_addr   <= x"0000";
                ctrl_mem_en     <= '1';
                ctrl_mem_we     <= '1';
                ctrl_mem_data   <= std_logic_vector(unsigned(current_task_count) + 1);

            when S_10_WAIT_FOR_COUNT =>
                next_state <= S_DONE;

            ------------------------------------------------------------
            -- OP = "11" (svuotamento)
            -- Scrive 0 in mem[0]: la lista risulta vuota, il contenuto
            -- delle celle task può essere ignorato.
            ------------------------------------------------------------
            when S_11_UPDATE_COUNT =>
                next_state      <= S_11_WAIT_FOR_COUNT;
                next_task_count <= (others => '0');
                next_mem_addr   <= (others => '0');
                ctrl_mem_en     <= '1';
                ctrl_mem_we     <= '1';
                ctrl_mem_data   <= (others => '0');

            when S_11_WAIT_FOR_COUNT =>
                next_state <= S_DONE;

        end case;
    end process;


    ----------------------------------------------------------------
    -- PROCESSO SINCRONO
    -- Registra stato e uscite sul fronte di salita del clock.
    -- Reset asincrono: porta la FSM in S_RESET con DONE = 1 e disabilita
    -- temporaneamente la memoria.
    ----------------------------------------------------------------
    process (i_clk, i_rst)
    begin
        if i_rst = '1' then
            state    <= S_RESET;
            o_done   <= '1';
            o_mem_en <= '0';
            o_mem_we <= '0';
        elsif rising_edge(i_clk) then
            state              <= next_state;
            current_mem_addr   <= next_mem_addr;
            current_task_count <= next_task_count;
            current_popped_id  <= next_popped_id;

            o_done     <= ctrl_done;
            o_mem_en   <= ctrl_mem_en;
            o_mem_we   <= ctrl_mem_we;
            o_mem_addr <= next_mem_addr;
            o_mem_data <= ctrl_mem_data;
            o_task_id  <= ctrl_task_id;
        end if;
    end process;

end FSM;
