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