`timescale 1 ns/1 ps
import test_pkg::*;
import const_bank_for_ver::*;
class ref_fft;

 // complex adder 
 local function t_COMPLEX add(t_COMPLEX a, b);
     add.r = a.r + b.r;
     add.i = a.i + b.i;
 endfunction


 // complex subtractor
 local function t_COMPLEX sub(t_COMPLEX a, b);
     sub.r = a.r - b.r;
     sub.i = a.i - b.i;
 endfunction


 // complex multiplier
 local function t_COMPLEX mul(t_COMPLEX a, b);
     mul.r = (a.r * b.r) - (a.i * b.i);
     mul.i = (a.r * b.i) + (a.i * b.r);
 endfunction


 //reverse addres bits
 local task get_data_addr (output integer   addr_data  [c_FFT_SIZE-1 : 0]);
  logic  [c_STAGE_COUNT-1 : 0] addr;
  for (int i = 0;i <c_FFT_SIZE; i++)
   begin
     addr         = i;               // convert address in bit values
     addr         = {<<{addr}};      // bit reverse
     addr_data[i] = integer'(addr);  // convert bit reversed address to integer
   end
 endtask


 // data order is reversed acording to bit reversed address 
 local task reverse_bit_data_order(input t_COMPLEX data_in   [c_FFT_SIZE-1 : 0],
                                   output t_COMPLEX data      [c_FFT_SIZE-1 : 0],
                                   input integer   addr_data [c_FFT_SIZE-1 : 0]);
  for (int i = 0; i < c_FFT_SIZE; i++)
   begin
     data[i] = data_in [addr_data[i]];
   end
  endtask


 // count constants for FFT and save them into constant bank
 local task count_const (output t_COMPLEX const_bank [c_FFT_SIZE/2 - 1 : 0]);
  real cmpl_angle;
  // real const_r;
  // real const_i;
  for (int addr = 0; addr < c_FFT_SIZE/2; addr++)
   begin
    // cmpl_angle = 3.14159 / real'(c_FFT_SIZE/2) * real'(addr);
    // const_r =  cos(cmpl_angle);
    // const_i = -sin(cmpl_angle);
    const_bank[addr] = '{c_CONST_VER_RE[addr],c_CONST_VER_IM[addr]};
    if (c_VERBOSE > 1)
     begin
      $write("DEBUG: Const gen: Addr: %%2d, const_re = %%4f, const_im = %%4f \n",
              addr, const_bank[addr].r, const_bank[addr].i);
     end
   end
 endtask


// FFT algorithm 
 local task count_FFT (input  t_COMPLEX data_in    [c_FFT_SIZE-1    : 0],
                       output t_COMPLEX data_out   [c_FFT_SIZE-1    : 0],
                       input  t_COMPLEX const_bank [c_FFT_SIZE/2 -1 : 0]);
  t_COMPLEX      constants;
  t_COMPLEX      temp1,temp2;
  t_COMPLEX      data  [c_FFT_SIZE-1 : 0];
  int block;

  data = data_in;
  for (int stage = 0; stage < c_STAGE_COUNT; stage++)
   begin
    for ( int addr = 0; addr < c_FFT_SIZE; addr++)  
      begin
        block = 0;
        while (block < (2**stage))
         begin
          constants = const_bank[2**(c_STAGE_COUNT-stage-1) * block];
          temp1 = add(data [addr], mul(constants, data[addr + 2**stage])); 
          temp2 = sub(data [addr], mul(constants, data[addr + 2**stage]));

          data[addr] = temp1;
          data[addr + 2**stage] = temp2;
        if (c_VERBOSE > 1)
         begin
          $write("DEBUG, ref_model: stage = %%1d addr = %%3d, data = %%4f.\n",
                  stage, addr, temp1);
          $write("DEBUG, ref_model: stage = %%1d addr = %%3d, data = %%4f.\n",
                  stage, addr + 2**stage, temp2);
         end
          addr  = addr  + 1;
          block = block + 1;
         end
       addr=addr+2**stage - 1;
     end
   end
   data_out = data;
endtask

// run FFT counting, task run previous local task in right order and return complex output
task run_FFT (input  t_COMPLEX data_in  [c_FFT_SIZE-1 : 0],
              output t_COMPLEX data_out [c_FFT_SIZE-1 : 0]);

  t_COMPLEX data       [c_FFT_SIZE-1 : 0];
  t_COMPLEX const_bank [c_FFT_SIZE/2 -1 : 0];
  integer   addr_data  [c_FFT_SIZE-1 : 0];

    get_data_addr(addr_data);
    reverse_bit_data_order(data_in, data, addr_data);
    count_const (const_bank);
    count_FFT(data, data_out, const_bank);	
endtask

endclass