-----------------------------------------------------------------------------
----------------------- FFT module definition package -----------------------
-----------------------------------------------------------------------------

library IEEE; 
use IEEE.STD_LOGIC_1164.all; 
use IEEE.NUMERIC_STD.all; 

 package fft_module_pkg is
-----------------------------------------------------------------------------
---------------- global constants - define design parameters ----------------
-----------------------------------------------------------------------------
--insert design parameters--
 -- size of FFT (= N) - derived from parameter c_STAGE_COUNT
 constant c_FFT_SIZE         : integer    := 2**c_STAGE_COUNT;

 -- size of constant bank, derived from size of FFT, always N/2
 constant c_CONST_COUNT      : integer    := 2**(c_STAGE_COUNT - 1);
 
-----------------------------------------------------------------------------
-------------- Type definition - define types used in design ----------------
-----------------------------------------------------------------------------

 -- Input and output data type (TOP -> FFT_MODULE -> TOP)
 type t_DATA_IN_OUT_FFT is array (2 * c_FFT_SIZE - 1 downto 0)
             of std_logic_vector (c_DATA_WIDTH - 1 downto 0);

 type t_LOAD_REG is array (c_FFT_SIZE - 1 downto 0)
             of std_logic_vector (c_DATA_WIDTH - 1 downto 0);

 -- complex number type, array of real and imaginary part
 type t_COMPLEX  is record 
      re        : signed  (c_DATA_WIDTH - 1 downto 0);
      im        : signed  (c_DATA_WIDTH - 1 downto 0);
 end record;

 -- array of complex numbers
 type t_COMPLEX_ARRAY is array (c_FFT_SIZE  - 1 downto 0)
      of t_COMPLEX; 

 -- constant bank data type
 type t_CONST_BANK is array (0 to c_CONST_COUNT - 1)
      of  signed  (c_DATA_WIDTH - 1  downto 0);

 -- latched data when synch aritmetic is used
 type t_SYNC_ASYNC_ARITHMETIC is record
      dr_hold   : std_logic;
      addr1     : std_logic_vector (c_STAGE_COUNT - 1 downto 0);
      addr2     : std_logic_vector (c_STAGE_COUNT - 1 downto 0);
      data1     : t_COMPLEX;
      data2     : t_COMPLEX;
 end record;

 -- state machine, states: wait for new data, loading data and counting data
 type t_DRIVING_LOGIC_STATE is (e_DONE, e_LOAD, e_COUNT, e_WAIT_ON_DATA);

-------------------------------------------------------------------------------
---------------------------- Function declaration -----------------------------
-------------------------------------------------------------------------------

-- FFT module arithmetic functions --------------------------------------------
-- Complex multiplication - get 2 complex number and return their product
 procedure cmpl_multiplier (signal cmpl_data_1 : in  t_COMPLEX;
                            signal cmpl_data_2 : in  t_COMPLEX;
                            signal mult_result : out t_COMPLEX);

-- Complex adding - get 2 complex number and return their sum
 procedure cmpl_adder (signal cmpl_data_1 : in  t_COMPLEX;
                       signal cmpl_data_2 : in  t_COMPLEX;
                       signal add_result  : out t_COMPLEX);

-- Complex subtracting - get 2 complex number and return their difference
 procedure cmpl_subtractor (signal cmpl_data_1 : in  t_COMPLEX;
                            signal cmpl_data_2 : in  t_COMPLEX;
                            signal sub_result  : out t_COMPLEX);

-- FFT module functions used for selecting and ordering data ------------------
-- Reverse bit order, gets value and return its bit reverse order value
 function bit_reverse(sig_in : integer) return integer;

-- choose which data are send to arithmetic part
 function get_data_from_reg (
                     data_reg  : t_COMPLEX_ARRAY;
                     data_addr : std_logic_vector (c_STAGE_COUNT - 1 downto 0))
                          return t_COMPLEX;

-- Driving logic functions ----------------------------------------------------
-- generate constant address according to actual stage & step in block
 function get_const_addr (
            stage      : std_logic_vector (c_STAGE_COUNTER_SIZE - 1 downto 0);
            block_step : std_logic_vector (c_STAGE_COUNT        - 2 downto 0))
                  return std_logic_vector;

end fft_module_pkg;

package body fft_module_pkg is

-------------------------------------------------------------------------------
---------------------------- Function definition ------------------------------
-------------------------------------------------------------------------------

-- FFT module arithmetic functions --------------------------------------------
 procedure cmpl_multiplier (signal cmpl_data_1 : in  t_COMPLEX;
                            signal cmpl_data_2 : in  t_COMPLEX;
                            signal mult_result : out t_COMPLEX) is
   variable temp1       : signed (2*c_DATA_WIDTH downto 0);
   variable temp2       : signed (2*c_DATA_WIDTH downto 0);
   variable temp3       : signed (2*c_DATA_WIDTH downto 0);
   variable res_temp_re : signed (2*c_DATA_WIDTH downto 0);
   variable res_temp_im : signed (2*c_DATA_WIDTH downto 0);
   variable cmpl_multiplier : t_COMPLEX;

  begin
   temp1 := (resize (cmpl_data_1.re, c_DATA_WIDTH + 1)
           + resize (cmpl_data_1.im, c_DATA_WIDTH + 1)) * cmpl_data_2.re;
   temp2 := (resize (cmpl_data_2.re, c_DATA_WIDTH + 1)
           + resize (cmpl_data_2.im, c_DATA_WIDTH + 1)) * cmpl_data_1.im;
   temp3 := (resize (cmpl_data_2.im, c_DATA_WIDTH + 1) 
           - resize (cmpl_data_2.re, c_DATA_WIDTH + 1)) * cmpl_data_1.re;

   -- cmpl_data_2(constants) is =< 1, result of multiplication cannot overflow
   res_temp_re := (temp1 - temp2);
   res_temp_im := (temp1 + temp3);

   cmpl_multiplier.re := res_temp_re (2 * (c_DATA_WIDTH - 1) - 1
                                   downto (c_DATA_WIDTH - 1) - 1);
   cmpl_multiplier.im := res_temp_im (2 * (c_DATA_WIDTH - 1) - 1
                                   downto (c_DATA_WIDTH - 1) - 1);
   mult_result <= cmpl_multiplier;
  end cmpl_multiplier;

 procedure cmpl_adder (signal cmpl_data_1 : in  t_COMPLEX;
                       signal cmpl_data_2 : in  t_COMPLEX;
                       signal add_result  : out t_COMPLEX) is
  begin
   add_result.re <= resize(shift_right((
                    resize(cmpl_data_1.re, c_DATA_WIDTH+1)
                  + resize(cmpl_data_2.re, c_DATA_WIDTH+1)),
                           1), c_DATA_WIDTH);
   add_result.im <= resize(shift_right((
                    resize(cmpl_data_1.im, c_DATA_WIDTH+1)
                  + resize(cmpl_data_2.im, c_DATA_WIDTH+1)),
                           1), c_DATA_WIDTH);
  end cmpl_adder;

 procedure cmpl_subtractor (signal cmpl_data_1 : in  t_COMPLEX;
                            signal cmpl_data_2 : in  t_COMPLEX;
                            signal sub_result  : out t_COMPLEX) is
  begin
   sub_result.re <= resize(shift_right((
                         resize(cmpl_data_1.re, c_DATA_WIDTH+1)
                       - resize(cmpl_data_2.re, c_DATA_WIDTH+1)),
                                1), c_DATA_WIDTH);
   sub_result.im <= resize(shift_right((
                         resize(cmpl_data_1.im, c_DATA_WIDTH+1)
                       - resize(cmpl_data_2.im, c_DATA_WIDTH+1)),
                                1), c_DATA_WIDTH);
  end cmpl_subtractor;
  
-- FFT module functions used for selecting and ordering data ------------------
 function bit_reverse(sig_in : integer) return integer is 
     variable sig_out_logic : unsigned (c_STAGE_COUNT-1 downto 0);
     variable sig_in_logic  : unsigned (c_STAGE_COUNT-1 downto 0);
  begin
    sig_in_logic := to_unsigned(sig_in, c_STAGE_COUNT);
    for i in c_STAGE_COUNT-1 downto 0 loop 
      sig_out_logic(i) := sig_in_logic(c_STAGE_COUNT - 1 - i);
    end loop; 
    return to_integer(sig_out_logic); 
  end bit_reverse;

  function get_data_from_reg(
             data_reg  : t_COMPLEX_ARRAY;
             data_addr : std_logic_vector (c_STAGE_COUNT - 1 downto 0))
                 return t_COMPLEX is
    variable addr : integer;
    variable addr_un : unsigned (c_STAGE_COUNT - 1 downto 0);
    variable data : t_COMPLEX;
   begin
      addr_un := unsigned(data_addr);
      addr := to_integer(addr_un);
      data := data_reg(addr);
     return data;
   end get_data_from_reg;

-- Functions for Driving logic module -----------------------------------------
  
  function get_const_addr (
             stage      : std_logic_vector (c_STAGE_COUNTER_SIZE - 1 downto 0);
             block_step : std_logic_vector (c_STAGE_COUNT        - 2 downto 0)) 
                   return std_logic_vector is

    variable const_addr : std_logic_vector (c_STAGE_COUNT        - 2 downto 0);
   begin
-- insert const addr case --
    return const_addr;
   end get_const_addr;

end fft_module_pkg;