`timescale 1 ns/1 ps
import test_pkg::*;

module FFT_top_BFM
      (
       input  logic  [c_DATA_WIDTH-1 : 0] DATA_out [2*c_FFT_SIZE-1 :0],      // output from FFT_top - serial data
       output logic  [c_DATA_WIDTH-1 : 0] DATA_in [2*c_FFT_SIZE-1 :0],       // input to FFT_top - serial data
       output wire     clk,           // main clock domain, used for counting FFT
       output wire     rst,           // system reset
       output wire     load_en        // enable loading data into FFT
      );

  logic    clk_en = 0;                    //enable clk generator
  logic    clk_var = 0;                   //clk signal
  logic    load_en_var = 0;               // load_en signal
  logic    reset = 0;                 //rst signal
  logic clk_data_in_en = 0;
    logic clk_data_in_var = 0;
  // t_DATA   DATA_in_var = 0;               //data_in signal
  logic  [c_DATA_WIDTH-1 : 0] DATA_in_var [2*c_FFT_SIZE-1 :0];

  time     clk_per = c_CLK_PER;       // period of clk signal
  int      data_en_per = c_DATA_EN_AFTER_CLK_PER; 
                                      //period of clk_data_in signal


 // enable clk generator
 task run_clk (integer clk_run); // 1 - enable
                                 // 0 - disable
  if (clk_run) clk_en = 1;
  else  clk_en = 0;
 endtask


 // set clk and clk_data_in period
 task set_clk_per (time clk_period,
                   int  clk_data_en_period);
  clk_per = clk_period;
  data_en_per = clk_data_en_period;
 endtask


 // perform reset, reset is active for 1 clk period
 task perform_reset (int synch_to_negedge); // 1 - synch to clk negedge
                                            // 0 - asynch release after 2 clk_per
   reset = 0;
  #(clk_per)
   reset = 1;
  #(clk_per)
  if (synch_to_negedge == 1)
   @ (negedge clk_var);
   reset = 0;
 endtask

 // send and receive data to/from FFT module
 task send_and_receive_data(
       input t_DATA_ARRAY send_data_re_array,
       input t_DATA_ARRAY send_data_im_array,
       output t_DATA_ARRAY received_data_array[1:0]);

  // 1. set first value, then enable clk_data_in generator and set load_en (enable loading)
//    DATA_in_var = send_data_re_array [0];
//   @ (negedge(clk_var))
//    load_en_var = 1'b1;
  @ (posedge(clk_var))
   clk_data_in_en = 1;

  // 2. sending data
  for (int i = 0; i < c_FFT_SIZE; i++)
   begin
  //  when falling edge of clk_data_in enable loading by load_en
  //  and set data_in defined in send_data_array
      DATA_in_var[2*i] = send_data_re_array [i];
      DATA_in_var[2*i+1] = send_data_im_array [i];
   end

  // 3. wait one count cycle
    @ (negedge(clk_var));
      load_en_var = 1'b1;
    @ (negedge(clk_var));
      load_en_var = 1'b0;

    repeat(2**(c_STAGE_COUNT-1)*c_STAGE_COUNT + 10)
	
   // wait until count cycle is finished
    @ (posedge(clk_var));

  // 3. receiving data from FFT module real part
  for (int i = 0; i < c_FFT_SIZE; i++)
   begin
  //  when rising edge of clk_data_in sample output data
  //  and save them into received_data_array
      received_data_array [0][i] = DATA_out[2*i];
      received_data_array [1][i] = DATA_out[2*i+1];
   end

  // 4. disable load_en and   clk_data_in_en
  endtask


 // clk generator
 always
  begin
   if (clk_en) begin
      clk_var = 1'b1;
     # (clk_per/2);
      clk_var = 1'b0;
     # (clk_per/2);
   end else begin
      clk_var = 1'b0;
    @ (clk_en);
	end
  end


 // clk_data_in generator, synchronize with rising edge of clk 
 always
  begin
  @ (posedge(clk_var));
   while (clk_data_in_en)
    begin
     #(clk_per/2);
     clk_data_in_var = 1'b1;
     @ (clk_var);
     #(clk_per/2);
     clk_data_in_var = 1'b0;
     repeat (data_en_per)
       @ (clk_var);
    end
     clk_data_in_var = 1'b0;
  end


 // output assignment, generated signals are assgined with output signals
 assign rst = reset;
 assign clk = clk_var;
 assign clk_data_en = clk_data_in_var;
 assign DATA_in = DATA_in_var;
 assign load_en = load_en_var;
endmodule