diff --git a/LAB2/sim/tb_img_conv.vhd b/LAB2/sim/tb_img_conv.vhd index 58af0cf..5aafbf2 100644 --- a/LAB2/sim/tb_img_conv.vhd +++ b/LAB2/sim/tb_img_conv.vhd @@ -17,90 +17,132 @@ -- Additional Comments: -- ---------------------------------------------------------------------------------- - - -library IEEE; -use IEEE.STD_LOGIC_1164.ALL; +LIBRARY IEEE; +USE IEEE.STD_LOGIC_1164.ALL; -- Uncomment the following library declaration if using -- arithmetic functions with Signed or Unsigned values -use IEEE.NUMERIC_STD.ALL; +USE IEEE.NUMERIC_STD.ALL; -- Uncomment the following library declaration if instantiating -- any Xilinx leaf cells in this code. --library UNISIM; --use UNISIM.VComponents.all; -entity img_conv_tb is --- Port ( ); -end img_conv_tb; +ENTITY img_conv_tb IS + -- Port ( ); +END img_conv_tb; -architecture Behavioral of img_conv_tb is +ARCHITECTURE Behavioral OF img_conv_tb IS - component img_conv is - generic( - LOG2_N_COLS: POSITIVE :=8; - LOG2_N_ROWS: POSITIVE :=8 + COMPONENT img_conv IS + GENERIC ( + LOG2_N_COLS : POSITIVE := 8; + LOG2_N_ROWS : POSITIVE := 8 ); - port ( - - clk : in std_logic; - aresetn : in std_logic; - - m_axis_tdata : out std_logic_vector(7 downto 0); - m_axis_tvalid : out std_logic; - m_axis_tready : in std_logic; - m_axis_tlast : out std_logic; - - conv_addr: out std_logic_vector(LOG2_N_COLS+LOG2_N_ROWS-1 downto 0); - conv_data: in std_logic_vector(6 downto 0); - - start_conv: in std_logic; - done_conv: out std_logic - + PORT ( + + clk : IN STD_LOGIC; + aresetn : IN STD_LOGIC; + + m_axis_tdata : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); + m_axis_tvalid : OUT STD_LOGIC; + m_axis_tready : IN STD_LOGIC; + m_axis_tlast : OUT STD_LOGIC; + + conv_addr : OUT STD_LOGIC_VECTOR(LOG2_N_COLS + LOG2_N_ROWS - 1 DOWNTO 0); + conv_data : IN STD_LOGIC_VECTOR(6 DOWNTO 0); + + start_conv : IN STD_LOGIC; + done_conv : OUT STD_LOGIC + ); - end component; + END COMPONENT; - constant LOG2_N_COLS: POSITIVE :=2; - constant LOG2_N_ROWS: POSITIVE :=2; + CONSTANT LOG2_N_COLS : POSITIVE := 2; + CONSTANT LOG2_N_ROWS : POSITIVE := 2; - type mem_type is array(0 to (2**LOG2_N_COLS)*(2**LOG2_N_ROWS)-1) of std_logic_vector(6 downto 0); + TYPE mem_type IS ARRAY(0 TO (2 ** LOG2_N_COLS) * (2 ** LOG2_N_ROWS) - 1) OF STD_LOGIC_VECTOR(6 DOWNTO 0); - signal mem : mem_type := (0=>"0000001",others => (others => '0')); + -- Fill memory with more varied values + SIGNAL mem : mem_type := ( + 0 => "0000001", + 1 => "0101010", + 2 => "0011100", + 3 => "1110001", + 4 => "0001011", + 5 => "0110110", + 6 => "1001001", + 7 => "1111111", + 8 => "0000111", + 9 => "0010010", + 10 => "0100101", + 11 => "0111000", + 12 => "1001100", + 13 => "1011011", + 14 => "1100110", + 15 => "1010101" + ); - signal clk : std_logic :='0'; - signal aresetn : std_logic :='0'; + SIGNAL clk : STD_LOGIC := '0'; + SIGNAL aresetn : STD_LOGIC := '0'; - signal m_axis_tdata : std_logic_vector(7 downto 0); - signal m_axis_tvalid : std_logic; - signal m_axis_tready : std_logic; - signal m_axis_tlast : std_logic; - - signal conv_addr: std_logic_vector(LOG2_N_COLS+LOG2_N_ROWS-1 downto 0); - signal conv_data: std_logic_vector(6 downto 0); + SIGNAL m_axis_tdata : STD_LOGIC_VECTOR(7 DOWNTO 0); + SIGNAL m_axis_tvalid : STD_LOGIC; + SIGNAL m_axis_tready : STD_LOGIC; + SIGNAL m_axis_tlast : STD_LOGIC; - signal start_conv: std_logic; - signal done_conv: std_logic; + SIGNAL conv_addr : STD_LOGIC_VECTOR(LOG2_N_COLS + LOG2_N_ROWS - 1 DOWNTO 0); + SIGNAL conv_data : STD_LOGIC_VECTOR(6 DOWNTO 0); -begin + SIGNAL start_conv : STD_LOGIC; + SIGNAL done_conv : STD_LOGIC; - m_axis_tready<='1'; + SIGNAL tready_block_req : STD_LOGIC := '0'; - clk <= not clk after 5 ns; +BEGIN - process (clk) - begin - if(rising_edge(clk)) then - conv_data<=mem(to_integer(unsigned(conv_addr))); - end if; - end process; + -- m_axis_tready logic with blocking step + PROCESS (clk) + VARIABLE block_counter : INTEGER := 0; + VARIABLE tready_blocked : BOOLEAN := FALSE; + BEGIN + IF rising_edge(clk) THEN + IF tready_block_req = '1' AND NOT tready_blocked THEN + tready_blocked := TRUE; + block_counter := 0; + END IF; - img_conv_inst: img_conv - generic map( + IF tready_blocked THEN + IF block_counter < 19 THEN + m_axis_tready <= '0'; + block_counter := block_counter + 1; + ELSE + m_axis_tready <= '1'; + tready_blocked := FALSE; + block_counter := 0; + END IF; + ELSE + m_axis_tready <= '1'; + END IF; + END IF; + END PROCESS; + + clk <= NOT clk AFTER 5 ns; + + PROCESS (clk) + BEGIN + IF (rising_edge(clk)) THEN + conv_data <= mem(to_integer(unsigned(conv_addr))); + END IF; + END PROCESS; + + img_conv_inst : img_conv + GENERIC MAP( LOG2_N_COLS => LOG2_N_COLS, LOG2_N_ROWS => LOG2_N_ROWS ) - port map( + PORT MAP( clk => clk, aresetn => aresetn, m_axis_tdata => m_axis_tdata, @@ -113,16 +155,31 @@ begin done_conv => done_conv ); - process - begin - wait for 10 ns; - aresetn<='1'; - wait until rising_edge(clk); - start_conv<='1'; - wait until rising_edge(clk); - start_conv<='0'; - wait; - end process; + PROCESS + BEGIN + WAIT FOR 10 ns; + aresetn <= '1'; + WAIT UNTIL rising_edge(clk); + start_conv <= '1'; + WAIT UNTIL rising_edge(clk); + start_conv <= '0'; + -- Wait some cycles, then request tready block for 10 cycles + WAIT FOR 200 ns; + tready_block_req <= '1'; + WAIT UNTIL rising_edge(clk); + tready_block_req <= '0'; -end Behavioral; + WAIT FOR 300 ns; + tready_block_req <= '1'; + WAIT UNTIL rising_edge(clk); + tready_block_req <= '0'; + + WAIT FOR 200 ns; + tready_block_req <= '1'; + WAIT UNTIL rising_edge(clk); + tready_block_req <= '0'; + + WAIT; + END PROCESS; +END Behavioral; \ No newline at end of file diff --git a/LAB2/src/img_conv.vhd b/LAB2/src/img_conv.vhd index 2a8ff72..f06a1c5 100644 --- a/LAB2/src/img_conv.vhd +++ b/LAB2/src/img_conv.vhd @@ -27,150 +27,314 @@ ENTITY img_conv IS END ENTITY img_conv; ARCHITECTURE rtl OF img_conv IS - TYPE conv_mat_type IS ARRAY(0 TO 2, 0 TO 2) OF INTEGER; - CONSTANT conv_mat : conv_mat_type := ((-1, -1, -1),(-1, 8, -1),(-1, -1, -1)); + CONSTANT conv_mat : conv_mat_type := ((-1, -1, -1), (-1, 8, -1), (-1, -1, -1)); - -- Definizione della finestra 3x3; ogni pixel è rappresentato da 8 bit - TYPE window_array IS ARRAY(0 TO 2, 0 TO 2) OF STD_LOGIC_VECTOR(7 DOWNTO 0); - SIGNAL window : window_array := (OTHERS => (OTHERS => (OTHERS => '0'))); + TYPE offset_array IS ARRAY(0 TO 2) OF INTEGER; + CONSTANT offset : offset_array := (-1, 0, 1); - -- Parametri immagine - CONSTANT IMG_WIDTH : INTEGER := 2 ** LOG2_N_COLS; -- Larghezza dell'immagine - CONSTANT IMG_HEIGHT : INTEGER := 2 ** LOG2_N_ROWS; -- Altezza dell'immagine - - -- Indirizzo corrente per la lettura dei pixel - SIGNAL current_addr : STD_LOGIC_VECTOR(LOG2_N_COLS + LOG2_N_ROWS - 1 DOWNTO 0); - - -- Variabili per il calcolo della convoluzione - SIGNAL conv_sum : INTEGER := 0; -- Somma dei prodotti - SIGNAL conv_out : STD_LOGIC_VECTOR(7 DOWNTO 0); -- Risultato della convoluzione - - -- Stato della macchina a stati - TYPE state_type IS (IDLE, LOAD_PIXEL, COMPUTE, OUTPUT); + TYPE state_type IS (IDLE, START_CONVOLUTING, CONVOLUTING, WAIT_READY); SIGNAL state : state_type := IDLE; - -- Contatori per riga e colonna - SIGNAL row, col : INTEGER RANGE 0 TO IMG_HEIGHT - 1 := 0; + SIGNAL col : UNSIGNED(LOG2_N_COLS - 1 DOWNTO 0) := (OTHERS => '0'); + SIGNAL row : UNSIGNED(LOG2_N_ROWS - 1 DOWNTO 0) := (OTHERS => '0'); + + SIGNAL col_mat_idx_prv : INTEGER := 0; + SIGNAL row_mat_idx_prv : INTEGER := 0; + + SIGNAL col_mat_idx : INTEGER := 0; + SIGNAL row_mat_idx : INTEGER := 0; + + SIGNAL col_mat_idx_nxt : INTEGER := 0; + SIGNAL row_mat_idx_nxt : INTEGER := 0; + + SIGNAL conv_data_out, conv_data_int, conv_data_mul : SIGNED(10 DOWNTO 0) := (OTHERS => '0'); + + SIGNAL m_axis_tvalid_int : STD_LOGIC; + + SIGNAL trigger, prepare_data, ready_data, send_data : STD_LOGIC := '0'; + SIGNAL tlast : STD_LOGIC := '0'; + SIGNAL save_data : STD_LOGIC := '0'; BEGIN - -- Aggiornamento dell'indirizzo di lettura (mapping riga-colonna) - conv_addr <= STD_LOGIC_VECTOR(to_unsigned(row * IMG_WIDTH + col, conv_addr'length)); + m_axis_tvalid <= m_axis_tvalid_int; - -- Processo principale: macchina a stati per la gestione della convoluzione - PROCESS (clk, aresetn) + PROCESS (clk) BEGIN - IF aresetn = '0' THEN - -- Reset asincrono: inizializza tutti i segnali - state <= IDLE; - row <= 0; - col <= 0; - window <= (OTHERS => (OTHERS => (OTHERS => '0'))); - conv_sum <= 0; - conv_out <= (OTHERS => '0'); - m_axis_tdata <= (OTHERS => '0'); - m_axis_tvalid <= '0'; - m_axis_tlast <= '0'; - done_conv <= '0'; - current_addr <= (OTHERS => '0'); - ELSIF rising_edge(clk) THEN - CASE state IS + IF rising_edge(clk) THEN + IF aresetn = '0' THEN - -- Stato IDLE: attende il segnale di start per iniziare la convoluzione - WHEN IDLE => - m_axis_tvalid <= '0'; - m_axis_tlast <= '0'; - IF start_conv = '1' THEN - row <= 0; - col <= 0; + -- Reset all signals + state <= IDLE; + + col <= (OTHERS => '0'); + row <= (OTHERS => '0'); + + col_mat_idx_nxt <= 0; + row_mat_idx_nxt <= 0; + + col_mat_idx_prv <= 0; + row_mat_idx_prv <= 0; + + col_mat_idx <= 0; + row_mat_idx <= 0; + + conv_data_out <= (OTHERS => '0'); + conv_data_int <= (OTHERS => '0'); + conv_data_mul <= (OTHERS => '0'); + + m_axis_tvalid_int <= '0'; + m_axis_tdata <= (OTHERS => '0'); + m_axis_tlast <= '0'; + + done_conv <= '0'; + + trigger <= '0'; + prepare_data <= '0'; + ready_data <= '0'; + send_data <= '0'; + tlast <= '0'; + save_data <= '0'; + + conv_addr <= (OTHERS => '0'); + + ELSE + -- Default values for signals + done_conv <= '0'; + m_axis_tlast <= '0'; + + CASE state IS + WHEN IDLE => + -- Default values in IDLE done_conv <= '0'; - -- Inizializza la finestra a zero per gestire il padding superiore - window <= (OTHERS => (OTHERS => (OTHERS => '0'))); - state <= LOAD_PIXEL; - END IF; + m_axis_tlast <= '0'; + m_axis_tvalid_int <= '0'; + m_axis_tdata <= (OTHERS => '0'); + conv_data_out <= (OTHERS => '0'); + conv_data_int <= (OTHERS => '0'); + conv_data_mul <= (OTHERS => '0'); + trigger <= '0'; + prepare_data <= '0'; + ready_data <= '0'; + send_data <= '0'; + tlast <= '0'; + save_data <= '0'; + conv_addr <= (OTHERS => '0'); - -- Stato LOAD_PIXEL: carica un nuovo pixel e aggiorna la finestra - WHEN LOAD_PIXEL => - -- Aggiorna la finestra con il nuovo pixel (conv_data) - -- Lo shifting viene realizzato per spostare i dati verso sinistra - IF col = 0 THEN - -- Per ogni riga della finestra si forza la colonna sinistra a zero - FOR i IN 0 TO 2 LOOP - window(i)(0) <= (OTHERS => '0'); - END LOOP; - ELSE - FOR i IN 0 TO 2 LOOP - window(i)(0) <= window(i)(1); - window(i)(1) <= window(i)(2); - END LOOP; - END IF; + IF start_conv = '1' THEN + state <= START_CONVOLUTING; - -- Aggiorna la colonna destra della finestra con il nuovo pixel - FOR i IN 0 TO 2 LOOP - window(i)(2) <= conv_data; - END LOOP; + -- Reset the convolution matrix position + row <= (OTHERS => '0'); + col <= (OTHERS => '0'); - -- Aggiorna i contatori per scorrere l'immagine - IF col = IMG_WIDTH - 1 THEN - col <= 0; - IF row = IMG_HEIGHT - 1 THEN - state <= COMPUTE; - ELSE - row <= row + 1; - END IF; - ELSE - col <= col + 1; - END IF; + -- Request the first pixel and set pointer to second pixel + row_mat_idx_prv <= 0; + col_mat_idx_prv <= 0; - state <= COMPUTE; + row_mat_idx <= 1; + col_mat_idx <= 1; - -- Stato COMPUTE: esegue il calcolo della convoluzione - WHEN COMPUTE => - -- Gestione dei bordi: imposta il risultato a zero se la finestra non è completa - IF (row = 0) OR (row = IMG_HEIGHT - 1) OR (col = 0) OR (col = IMG_WIDTH - 1) THEN - conv_sum <= 0; - ELSE - conv_sum <= 0; - -- Moltiplica cella per cella e somma i prodotti - FOR i IN 0 TO 2 LOOP - FOR j IN 0 TO 2 LOOP - conv_sum <= conv_sum + conv_mat(i, j) * to_integer(unsigned(window(i)(j))); - END LOOP; - END LOOP; - END IF; + row_mat_idx_nxt <= 1; + col_mat_idx_nxt <= 2; - -- Saturazione del risultato - IF conv_sum < 0 THEN - conv_out <= STD_LOGIC_VECTOR(to_unsigned(0, 8)); - ELSIF conv_sum >= 256 THEN - conv_out <= STD_LOGIC_VECTOR(to_unsigned(255, 8)); - ELSE - conv_out <= STD_LOGIC_VECTOR(to_unsigned(conv_sum, 8)); - END IF; - - state <= OUTPUT; - - -- Stato OUTPUT: invia il risultato tramite l'interfaccia AXIS - WHEN OUTPUT => - IF m_axis_tready = '1' THEN - m_axis_tdata <= conv_out; - m_axis_tvalid <= '1'; - -- Se siamo sull'ultimo pixel dell'immagine, segnaliamo TLAST e il completamento - IF (row = IMG_HEIGHT - 1) AND (col = IMG_WIDTH - 1) THEN - m_axis_tlast <= '1'; - done_conv <= '1'; - state <= IDLE; - ELSE + conv_addr <= (OTHERS => '0'); + conv_data_out <= (OTHERS => '0'); + conv_data_int <= (OTHERS => '0'); + conv_data_mul <= (OTHERS => '0'); + trigger <= '0'; + prepare_data <= '0'; + ready_data <= '0'; + send_data <= '0'; + tlast <= '0'; + save_data <= '0'; + m_axis_tvalid_int <= '0'; + m_axis_tdata <= (OTHERS => '0'); m_axis_tlast <= '0'; - state <= LOAD_PIXEL; + done_conv <= '0'; END IF; + + WHEN START_CONVOLUTING => + conv_addr <= STD_LOGIC_VECTOR( + TO_UNSIGNED( + (TO_INTEGER(col) + offset(col_mat_idx_nxt)) + + (TO_INTEGER(row) + offset(row_mat_idx_nxt)) * (2 ** LOG2_N_COLS), + conv_addr'length + ) + ); + + state <= CONVOLUTING; + + WHEN CONVOLUTING => + -- Convolution operation: accumulate the result of current pixel and kernel coefficient + conv_addr <= STD_LOGIC_VECTOR( + TO_UNSIGNED( + (TO_INTEGER(col) + offset(col_mat_idx_nxt)) + + (TO_INTEGER(row) + offset(row_mat_idx_nxt)) * (2 ** LOG2_N_COLS), + conv_addr'length + ) + ); + + conv_data_mul <= RESIZE( + SIGNED('0' & conv_data) * TO_SIGNED(conv_mat(col_mat_idx_prv, row_mat_idx_prv), 5), + conv_data_mul'length + ); + + IF ready_data = '1' THEN + conv_data_out <= conv_data_int + conv_data_mul; + conv_data_int <= (OTHERS => '0'); + ELSE + conv_data_int <= conv_data_int + conv_data_mul; + END IF; + + trigger <= '0'; + + WHEN WAIT_READY => + -- Wait for m_axis_tready signal to be asserted before sending data and continue convolution + IF m_axis_tready = '1' THEN + conv_addr <= STD_LOGIC_VECTOR( + TO_UNSIGNED( + (TO_INTEGER(col) + offset(col_mat_idx_nxt)) + + (TO_INTEGER(row) + offset(row_mat_idx_nxt)) * (2 ** LOG2_N_COLS), + conv_addr'length + ) + ); + + save_data <= '0'; + state <= CONVOLUTING; + END IF; + + IF save_data = '0' THEN + conv_data_mul <= RESIZE( + SIGNED('0' & conv_data) * TO_SIGNED(conv_mat(col_mat_idx_prv, row_mat_idx_prv), 5), + conv_data_mul'length + ); + + IF ready_data = '1' THEN + conv_data_out <= conv_data_int + conv_data_mul; + conv_data_int <= (OTHERS => '0'); + ELSE + conv_data_int <= conv_data_int + conv_data_mul; + END IF; + + save_data <= '1'; + END IF; + + END CASE; + + -- Output data - master + IF m_axis_tready = '1' THEN + m_axis_tvalid_int <= '0'; + END IF; + + -- Wait for m_axis_tready signal to be asserted before continuing convolution + IF m_axis_tready = '0' AND m_axis_tvalid_int = '1' THEN + state <= WAIT_READY; + END IF; + + IF send_data = '1' AND (m_axis_tvalid_int = '0' OR m_axis_tready = '1') THEN + m_axis_tvalid_int <= '1'; + + IF tlast = '1' THEN + state <= IDLE; + done_conv <= '1'; + m_axis_tlast <= '1'; + tlast <= '0'; END IF; - -- Stato di default: ritorna a IDLE - WHEN OTHERS => - state <= IDLE; - END CASE; + IF conv_data_out < 0 THEN + m_axis_tdata <= STD_LOGIC_VECTOR(TO_UNSIGNED(0, m_axis_tdata'length)); + ELSIF conv_data_out > 127 THEN + m_axis_tdata <= STD_LOGIC_VECTOR(TO_UNSIGNED(127, m_axis_tdata'length)); + ELSE + m_axis_tdata <= STD_LOGIC_VECTOR(conv_data_out(7 DOWNTO 0)); + END IF; + + -- Reset accumulator and trigger + conv_data_out <= (OTHERS => '0'); + send_data <= '0'; + END IF; + + IF state = CONVOLUTING OR state = START_CONVOLUTING OR (state = WAIT_READY AND m_axis_tready = '1') THEN + -- Update convolution matrix position, image position and check for zero padding + IF col_mat_idx_nxt = 1 AND col = (2 ** LOG2_N_COLS - 1) THEN + IF row_mat_idx_nxt = 1 AND row = (2 ** LOG2_N_ROWS - 1) THEN + -- Last pixel and last kernel position: finish convolution + IF tlast = '0' THEN + trigger <= '1'; -- Send last data + tlast <= '1'; + END IF; + + ELSIF row_mat_idx_nxt = 2 THEN + -- End of kernel, move to next image row + col <= (OTHERS => '0'); + row <= row + 1; + + row_mat_idx_nxt <= 0; + col_mat_idx_nxt <= 1; -- new row adding padding + + trigger <= '1'; -- Send data + + ELSE + -- Move to next kernel row + row_mat_idx_nxt <= row_mat_idx_nxt + 1; + col_mat_idx_nxt <= 0; + + END IF; + + ELSIF col_mat_idx_nxt = 2 THEN + IF row_mat_idx_nxt = 1 AND row = (2 ** LOG2_N_ROWS - 1) THEN + -- End of kernel column at last image row, move to next image column + col <= col + 1; + + row_mat_idx_nxt <= 0; + col_mat_idx_nxt <= 0; + + trigger <= '1'; -- Send data + + ELSIF row_mat_idx_nxt = 2 THEN + -- End of kernel column and row, move to next image column + col <= col + 1; + + IF row = 0 THEN + row_mat_idx_nxt <= 1; -- first row adding padding + ELSE + row_mat_idx_nxt <= 0; + END IF; + col_mat_idx_nxt <= 0; + + trigger <= '1'; -- Send data + + ELSE + -- Move to next kernel column + IF col = 0 THEN + col_mat_idx_nxt <= 1; -- first column adding padding + ELSE + col_mat_idx_nxt <= 0; + END IF; + row_mat_idx_nxt <= row_mat_idx_nxt + 1; + + END IF; + + ELSE + -- Continue kernel sweep: increment kernel column index + col_mat_idx_nxt <= col_mat_idx_nxt + 1; + + END IF; + + prepare_data <= trigger; -- Pipeline trigger for data output waiting for last data + ready_data <= prepare_data; + send_data <= ready_data; + + row_mat_idx_prv <= row_mat_idx; + col_mat_idx_prv <= col_mat_idx; + + row_mat_idx <= row_mat_idx_nxt; + col_mat_idx <= col_mat_idx_nxt; + END IF; + + END IF; END IF; END PROCESS; diff --git a/LAB2/vivado/img_conv_test/img_conv_test.xpr b/LAB2/vivado/img_conv_test/img_conv_test.xpr new file mode 100644 index 0000000..a7724d6 --- /dev/null +++ b/LAB2/vivado/img_conv_test/img_conv_test.xpr @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default_dashboard + + +