-----------------------------------------------------------------------------
-------------- FFT module - implement Fast Fourier Transform ----------------
-----------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

use work.fft_module_pkg.all;
use work.const_bank_pkg.all;

entity fft_module is
 Port (      
      -- input data - time domain
       DATA_in     : in  t_DATA_IN_OUT_FFT;
      -- clock and reset signals
       clk         : in  std_logic;
       rst         : in  std_logic;
      -- load enable
       load_en     : in  std_logic;
      -- output data - frequency domain
       DATA_out    : out t_DATA_IN_OUT_FFT
       );

end fft_module;

architecture Structural of fft_module is

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

 -- selected data from main register (into multiplier, adder)
  signal data1           : t_COMPLEX;
  signal data2           : t_COMPLEX;

 -- signal from constant bank (into multiplier)
  signal const           : t_COMPLEX;

 -- signals from complex multiplier
  signal mull_out        : t_COMPLEX;

 -- signals from complex adder
  signal add_out         : t_COMPLEX;

 -- signals from complex subtractor
  signal sub_out         : t_COMPLEX;

 -- main data register signal array
  signal data_reg_out    : t_COMPLEX_ARRAY;
  signal data_reg_in     : t_COMPLEX_ARRAY;
  signal data_reg_load   : t_COMPLEX_ARRAY;
  
 -- select data to write into data register (into multiplexer_data_reg_in)
  signal data_reg_in_add : std_logic_vector (c_FFT_SIZE - 1    downto 0);
  signal data_reg_in_sub : std_logic_vector (c_FFT_SIZE - 1    downto 0);

 -- trigger for loading new data from input register
  signal data_reg_load_en: std_logic;
  
 -- disable writing new data into main data register, active when count ends
  signal data_reg_hold   : std_logic;

 -- select data from data register (drives data1, data2)
  signal data1_addr     : std_logic_vector (c_STAGE_COUNT - 1 downto 0);
  signal data2_addr     : std_logic_vector (c_STAGE_COUNT - 1 downto 0);

-- constant address (into constant bank)
  signal const_addr      : std_logic_vector (c_STAGE_COUNT - 2 downto 0);

 -- one clock delay in arithmetic part between multiplier and adder/subtractor
 -- + data register input multiplexors driving signals one clock delay
  signal arith_delay_c   : t_SYNC_ASYNC_ARITHMETIC;
  signal arith_delay_s   : t_SYNC_ASYNC_ARITHMETIC;

-------------------------------------------------------------------------------
-------------------------- Component declaration ------------------------------
-------------------------------------------------------------------------------

  -- driving logic, implements FFT algorithm
  component driving_logic
    Port (
         -- clock, reset and load enable from higher module
          clk            : in  std_logic;
          rst            : in  std_logic;
          load_en        : in  std_logic;    
         -- selecting data from main 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 form input register
          data_reg_load  : out std_logic;
         -- hold ceonted data in main data register until new count cycle starts
          data_reg_hold  : out std_logic;
         -- select constant form constant bank
          const_addr     : out std_logic_vector (c_STAGE_COUNT-2 downto 0)
          );
  end component;
 
 begin

-------------------------------------------------------------------------------
----------------- Structural part + component connection ----------------------
-------------------------------------------------------------------------------

 -- complex multiplier - procedure defined in fft_module_pkg
  cmpl_multiplier(data2, const, mull_out);

 -- complex adder - procedure defined in fft_module_pkg
  cmpl_adder (arith_delay_s.data1, arith_delay_s.data2, add_out);

 -- complex subtractor - procedure defined in fft_module_pkg
  cmpl_subtractor (arith_delay_s.data1, arith_delay_s.data2, sub_out);

 -- main data register
  main_data_reg :  process (rst, clk)
   begin
    if (rst = '1') then
     -- if reset, set 0 in all registers
     data_reg_out <= (others => (others =>(others => '0')));
     -- if rising edge, data are loaded
    elsif (rising_edge(clk)) then
     data_reg_out <= data_reg_in;
    end if;
   end process;

 -- select data to write into main register, array of multiplexors
  data2data_reg_selector:
   for addr in 0 to c_FFT_SIZE - 1 generate
                       -- load new data - highest priority
   data_reg_in(addr) <= data_reg_load(bit_reverse(addr))
                          when data_reg_load_en = '1'      else
                       -- load data from adder
                        add_out
                          when data_reg_in_add(addr) = '1' else
                       -- load data from subtractor
                        sub_out
                          when data_reg_in_sub(addr) = '1' else
                       -- default data are data from main reg output
                        data_reg_out(addr);
  end generate;

  -- driving logic generates address of data and drives loading/sending
  -- new/counted data.
  fft_algorithm: driving_logic
   port map(
       -- clock and reset signals
        clk             => clk,
        rst             => rst,
        load_en         => load_en,
       -- driving data from register
        data1_addr      => data1_addr,
        data2_addr      => data2_addr,
       -- load data from input register
        data_reg_load   => data_reg_load_en,
        data_reg_hold   => data_reg_hold,
       -- select constant form constant bank
        const_addr      => const_addr
           );

 -- One clock delay in arithmetic part

  arith_delay_c.dr_hold   <= data_reg_hold;
  arith_delay_c.addr1     <= data1_addr;
  arith_delay_c.addr2     <= data2_addr;
  arith_delay_c.data1     <= data1;
  arith_delay_c.data2     <= mull_out;

  process (arith_delay_c.dr_hold, arith_delay_c.data1, arith_delay_c.data2,
           arith_delay_c.addr1, arith_delay_c.addr2,  clk, rst)
   begin
   -- if latches in artih part are not enabled, signals are connected
   -- without clock delay.
    if (c_SYNC_ASYNC_ARITHMETIC = 0) then 
      arith_delay_s <= arith_delay_c;
    else
     if (rst = '1') then
      arith_delay_s.dr_hold <= '0';
      arith_delay_s.addr1   <= (others => '0');
      arith_delay_s.addr2   <= (others => '0');
      arith_delay_s.data1   <= (others =>(others => '0'));
      arith_delay_s.data2   <= (others =>(others => '0'));
     elsif rising_edge(clk) then
      arith_delay_s <= arith_delay_c;
     end if;
    end if;
   end process;

 -- Constant selector
  const.re <= c_CONST_RE(to_integer(unsigned(const_addr)));
  const.im <= c_CONST_IM(to_integer(unsigned(const_addr)));

 -- data 1 and data 2 from data reg selector
  data1 <= get_data_from_reg(data_reg_out, data1_addr);
  data2 <= get_data_from_reg(data_reg_out, data2_addr);

   -- encoder from c_STAGE_COUNT size to c_FFT_SIZE size (one-hot)
  encoder: process (arith_delay_s, data_reg_load_en, arith_delay_s)
     variable data_reg_in_add_var : std_logic_vector (c_FFT_SIZE - 1 downto 0);
     variable data_reg_in_sub_var : std_logic_vector (c_FFT_SIZE - 1 downto 0);
    begin
--      if (data_reg_load_en = '1') then
--       data_reg_in_add_var := (others => '0');
--       data_reg_in_sub_var := (others => '0');
   -- if all data are counted, disable loading data to main reg from arith part
     if (arith_delay_s.dr_hold = '1') then
      data_reg_in_add_var := (others => '0'); 
      data_reg_in_sub_var := (others => '0');
   -- one - hot encoding is used to drive one multiplexor in multiplexor array
     else
      data_reg_in_add_var := (others => '0');
      data_reg_in_add_var(to_integer(unsigned(arith_delay_s.addr1))) := '1';
      data_reg_in_sub_var := (others => '0'); 
      data_reg_in_sub_var(to_integer(unsigned(arith_delay_s.addr2))) := '1';
     end if;
       data_reg_in_add <= data_reg_in_add_var;
       data_reg_in_sub <= data_reg_in_sub_var;
  end process;

-- incoming data ordering - here Re Im Re Im Re Im ... Re Im
 order_loaded_data : for addr in 0 to c_FFT_SIZE - 1 generate
  data_reg_load(addr).re <= signed(DATA_in (addr*2));
  data_reg_load(addr).im <= signed(DATA_in (addr*2 + 1));
 end generate;

--  data from data reg to output reg - Re Im Re Im ... Re Im Re
   output_reg_load: for addr in 0 to c_FFT_SIZE - 1
    generate
     DATA_out (addr * 2)     <= std_logic_vector(data_reg_out(addr).re);
     DATA_out (addr * 2 + 1) <= std_logic_vector(data_reg_out(addr).im);
    end generate;

end Structural;