-----------------------------------------------------------------------------
--------------- Driving logic - implements algorithm of FFT -----------------
-----------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

use work.fft_module_pkg.all;
 
entity driving_logic is
  Port(
      -- clock and reset signals
       clk             : in  std_logic;
       rst             : in  std_logic;
       load_en         : in  std_logic;     -- rising edge starts FFT count
      -- driving data from register - address in data register
       data1_addr     : out std_logic_vector (c_STAGE_COUNT - 1 downto 0);
       data2_addr     : out std_logic_vector (c_STAGE_COUNT - 1 downto 0);
      -- load data from input register when loading in Data in shift register is finished
       data_reg_load   : out std_logic;
      -- hold values in Data register until they are loaded into Data out shift register
       data_reg_hold   : out std_logic;
      -- select constant form constant bank
       const_addr      : out std_logic_vector (c_STAGE_COUNT - 2 downto 0)
       );
end driving_logic;

architecture Behavioral of driving_logic is

-------------------------------------------------------------------------------
--------------------------- Signal declaration --------------------------------
-------------------------------------------------------------------------------

 -- state machine signals
  signal present_state       : t_DRIVING_LOGIC_STATE; 
  signal next_state          : t_DRIVING_LOGIC_STATE; 
  -- signal count_done          : std_logic;

 --progress counter signals
  signal progress_cnt_s      : std_logic_vector (c_STAGE_COUNT - 2 downto 0);
  signal progress_cnt_c      : std_logic_vector (c_STAGE_COUNT - 2 downto 0);

 -- stage counter signals
  signal stage_s             : std_logic_vector(c_STAGE_COUNTER_SIZE - 1 downto 0);
  signal stage_c             : std_logic_vector(c_STAGE_COUNTER_SIZE - 1 downto 0);
  signal stage_done          : std_logic; 

 -- address counter
  signal data1_addr_sig      : std_logic_vector (c_STAGE_COUNT - 1 downto 0);
  signal data2_addr_sig      : std_logic_vector (c_STAGE_COUNT - 1 downto 0);
  signal block_step_count_c  : std_logic_vector (c_STAGE_COUNT - 2 downto 0);
  signal block_step_count_s  : std_logic_vector (c_STAGE_COUNT - 2 downto 0);
  signal block_done          : std_logic;
  signal next_block_addr_c   : std_logic_vector (c_STAGE_COUNT - 2 downto 0);
  signal next_block_addr_s   : std_logic_vector (c_STAGE_COUNT - 2 downto 0);

 -- constant address
  signal const_addr_sig      : std_logic_vector (c_STAGE_COUNT - 2 downto 0);

 begin

-------------------------------------------------------------------------------
------------------------------- State machine ---------------------------------
-------------------------------------------------------------------------------

  state_machine_s: process (clk, next_state, rst) 
   begin
    if rst= '1' then
      present_state <= e_DONE;
    elsif rising_edge(clk) then
      present_state <= next_state;
    end if;
   end process;
  
  state_machine_c: process (present_state, load_en, stage_s, progress_cnt_s) 
   begin
     data_reg_load <= '0';
     data_reg_hold <= '0';
    case present_state is
     when e_LOAD => 
       next_state <= e_COUNT;
       data_reg_load <= '1';
     when e_COUNT => 
      next_state <= e_COUNT;
      if (stage_s = c_STAGE_COUNT - 1) then
       if (progress_cnt_s  = (c_FFT_SIZE/2 - 1)) then
        next_state <= e_DONE;
       end if;
      end if;
     when e_DONE => 
      data_reg_hold <= '1';
      if load_en = '1' then
       next_state <= e_DONE;
      else
       next_state <= e_WAIT_ON_DATA;
      end if;
     when e_WAIT_ON_DATA =>
      data_reg_hold <= '1';
      if load_en = '1' then
       next_state <= e_LOAD;
      else
       next_state <= e_WAIT_ON_DATA;
      end if;
     when others =>
      next_state <= e_DONE;
    end case;
   end process;
 
-------------------------------------------------------------------------------
------------------------------- FFT algotithm ---------------------------------
-------------------------------------------------------------------------------
 -- stage_counter
  stage_counter_s: process (clk, stage_c, rst, present_state)
   begin
    if rst = '1' then
     stage_s <= (others => ('0'));
    elsif rising_edge(clk) then
     stage_s <= stage_c;
    end if;
   end process;

  stage_counter_c: process (stage_s, stage_done)
   begin
    if (stage_done = '1') then
     if (stage_s = c_STAGE_COUNT - 1) then
        stage_c <= (others => ('0'));
     else 
        stage_c <= stage_s + 1;
     end if;
    else 
     stage_c <= stage_s;
    end if; 
   end process;

-- progress counter - counts steps 
  progress_counter_s: process(clk,rst,progress_cnt_c)
   begin
    if rst = '1' then
     progress_cnt_s <= (others => '0');
    elsif rising_edge(clk) then
     progress_cnt_s <= progress_cnt_c;
    end if;
   end process;

  progress_counter_c: process(progress_cnt_s, stage_s, present_state)
   begin
     stage_done <= '0';
    if present_state = e_COUNT then
     if progress_cnt_s = (c_FFT_SIZE/2 - 1) then
       progress_cnt_c <= (others => '0');
       stage_done <= '1';
     else
       progress_cnt_c <= progress_cnt_s + 1;
     end if;
    else
       progress_cnt_c <= (others => '0');
    end if;
   end process;
 
 -- data driver - drives data from data reg into aritmetic block
  block_step_counter_s: process (clk, rst, block_step_count_c)
   begin
    if rst = '1' then
      block_step_count_s <= (others => '0');
    elsif rising_edge(clk) then 
      block_step_count_s <= block_step_count_c;
    end if;
   end process;

  block_step_counter_c: process (block_step_count_s, stage_s, present_state)
   begin
     block_done <= '0';
    if present_state = e_COUNT then
     if (block_step_count_s = (2**(to_integer(unsigned(stage_s))))-1) then--get_block_step_max(stage_s)) then
       block_done <= '1';
       block_step_count_c <= (others => '0');
     else
       block_step_count_c <= block_step_count_s + 1;
     end if;
    else
      block_step_count_c <= (others => '0');
    end if;
   end process;
 
  next_block_start_s: process (rst, clk, block_done, next_block_addr_c)
   begin
    if rst = '1' then
     next_block_addr_s <= (others => '0');
    elsif rising_edge(clk) and block_done = '1' then
     next_block_addr_s <= next_block_addr_c;
    end if;
   end process;

  next_block_start_c: process (stage_s, next_block_addr_s, stage_done)
   begin
     if stage_done = '1' then
      next_block_addr_c <= (others => '0');
     else
      next_block_addr_c <= (next_block_addr_s + (2**(to_integer(unsigned(stage_s)))));
     end if;
   end process;
   
 -- generate address of data register - read, latched - write
  data_driver: process (next_block_addr_s, stage_s, progress_cnt_s) 
    variable data1_addr_var : std_logic_vector (c_STAGE_COUNT - 1 downto 0);
    variable data2_addr_var : std_logic_vector (c_STAGE_COUNT - 1 downto 0);
   begin
     data1_addr_var := ('0' & progress_cnt_s) + next_block_addr_s;
     data2_addr_var := data1_addr_var + (2**(to_integer(unsigned(stage_s))));
  -- data address to out
     data1_addr_sig <= data1_addr_var;
     data2_addr_sig <= data2_addr_var;
   end process;
 
-- generate constant address
  constant_drive: process (stage_s,block_step_count_s)
   begin
     const_addr_sig <= get_const_addr (stage_s, block_step_count_s);
   end process;

  data1_addr  <= data1_addr_sig;
  data2_addr  <= data2_addr_sig;
  const_addr   <= const_addr_sig;
  
 end Behavioral;