library IEEE;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_textio.all;

library ieee;
use std.textio.all;


entity tb_vec_by_circ5_opt is
generic (SAMPLES_LOG2: integer := 5);
end tb_vec_by_circ5_opt;

architecture Behavioral of tb_vec_by_circ5_opt is

component Counter is
    generic (constant N: integer := 8);
    Port ( Rst_n : in std_logic;
           Clk : in std_logic;
           En : in std_logic;
           CntRst: in std_logic; -- When 1 reset the counte to 0 sync reset.
           CntValue : out unsigned(N-1 downto 0);
           TC : out std_logic);
end component;

--- Default Constants
constant DUT_IN_WIDTH: integer := 16;

-----------------------------------------------------------------------
-- Timing constants
-----------------------------------------------------------------------
constant CLOCK_PERIOD : time := 2 ns; -- 500Mhz Clock
constant T_HOLD       : time := 200 ps;
constant T_STROBE     : time := CLOCK_PERIOD - (20 ps);

constant NUM_OF_SAMPLES: integer := 2**SAMPLES_LOG2;

-- A big number because as far as I know ther eis no way to have a synamic list in vhdl.
constant SAMPLE_FILE_LINES_LOG2: integer := 10;
constant SAMPLE_FILE_LINES: integer := 2**SAMPLE_FILE_LINES_LOG2; 
type out_memory_type is array(0 to SAMPLE_FILE_LINES-1) of std_logic_vector(0 to NUM_OF_SAMPLES-1);
type sample_memory_type is array(0 to SAMPLE_FILE_LINES-1) of std_logic_vector(0 to 2*NUM_OF_SAMPLES-1);

signal out_memory: out_memory_type := (others => (others => '0'));
-- This file contain the theretical results
signal ref_memory: out_memory_type := (others => (others => '0'));
-- This contain the input samples for both a and b.
signal sample_memory: sample_memory_type := (others => (others => '0'));


signal mem_cnt_en:  std_logic := '0';
signal mem_cnt_tc:  std_logic := '0';
signal mem_cnt_rst: std_logic := '0';
signal mem_cnt_val: unsigned(SAMPLE_FILE_LINES_LOG2-1 downto 0) := (others => '0'); 

-- TB Signals
signal Clk : std_logic := '0';  -- the master clock
signal Rst_n: std_logic := '0';

signal f_cnt_en:  std_logic := '0';
signal f_cnt_tc:  std_logic := '0';
signal f_cnt_rst: std_logic := '0';
signal f_cnt_val: unsigned(SAMPLES_LOG2-1 downto 0) := (others => '0'); 

signal data_in_rdy: std_logic := '0';

signal dut_a_inputs: std_logic_vector(0 to NUM_OF_SAMPLES-1);
signal dut_b_inputs: std_logic_vector(0 to NUM_OF_SAMPLES-1);
signal out_data_vector: std_logic_vector(0 to NUM_OF_SAMPLES-1) := (others => '0'); 



-- TB state machine
type FSM_State is (ResetState, IdleState,  WaitConfigState, 
     SendDataState, LoadSampleState, SaveOutState, 
     SaveFileState, DoneState);
signal CurrState, NextState: FSM_State := ResetState;

-- DUT signals
signal dut_start: std_logic := '0';
signal dut_config_done: std_logic := '0';
signal dut_last_frame: std_logic := '0';


signal dut_done: std_logic := '0';
signal dut_error: std_logic := '0';
signal dut_a_data, dut_b_data: std_logic := '0';

signal dut_bin_out: std_logic := '0';
signal dut_out_valid: std_logic := '0';
signal dut_out_last: std_logic := '0';
signal dut_data_in_rdy: std_logic := '0';
signal dut_bin_val: unsigned(1 downto 0) := (others => '0');

file in_data_file: text;
file ref_data_file: text;
file out_data_file: text;

signal dut_global_start: std_logic := '0';
signal done_read_samples: std_logic := '0';
signal file_last_line_idx: integer := 0; 

signal done_simulate: std_logic;

signal sample_idx: integer := 0;

begin

-- CLock And reset generation
-----------------------------------------------------------------------
-- Generate clock
-----------------------------------------------------------------------
clock_gen : process
begin
    clk <= '0';
    wait for CLOCK_PERIOD;
    loop
        clk <= '0';
        wait for CLOCK_PERIOD/2;
        clk <= '1';
        wait for CLOCK_PERIOD/2;
    end loop;
end process clock_gen;

-- Generate the Reset signal
process
begin  -- process
    --- Wait untile the sample memory is not loaded.
    wait until done_read_samples='1';

	Rst_n <= '0';
    wait for 3*CLOCK_PERIOD/2;
	Rst_n <= '1';
    wait;
end process;

--- State Machine Update circuit
process(Clk, Rst_n) 
begin
    if Rst_n='0' then
        CurrState <= ResetState;
    elsif rising_edge(Clk) then
        CurrState <= NextState;
    end if;
end process;

-- This process read the input config file and load it into the memory
-- to later by used by the main process.
process (Rst_n)
    variable ref_line: line;
    variable current_line: line;
    
    variable ref_idx: integer := 0;
    variable space_char: character;
    variable curr_idx: integer := 0;
    variable is_done: std_logic := '0';
    
    variable ref_data: std_logic_vector(0 to 2**SAMPLES_LOG2-1) := (others => '0');
    variable input_a_data: std_logic_vector(0 to 2**SAMPLES_LOG2-1) := (others => '0');
    variable input_b_data: std_logic_vector(0 to 2**SAMPLES_LOG2-1) := (others => '0');
    
begin
   if Rst_n='0' then
        if is_done='0' then
             file_open(in_data_file, "W:\\proj_thesis\\hw\\fpga\\fft_vec_by_circ\\tb\\sim\\data_mat_32.txt", read_mode); 
             file_open(ref_data_file, "W:\\proj_thesis\\hw\\fpga\\fft_vec_by_circ\\tb\\sim\\result_mat_32.txt", read_mode); 
                           
             while not endfile(in_data_file) loop
                readline(in_data_file,  current_line);
             
                -- # is a comment
                if current_line(1) = '#' then
                    -- Nothing to do it's a comment.
                else
                -- Read the file data into the dut_a_inputs/dut_b_inputs                
                    read(current_line, input_a_data);
                  
                -- Read the space between the two data.
                    read(current_line, space_char);
                    read(current_line, input_b_data);
                    
                    sample_memory(curr_idx) <= input_a_data & input_b_data;
                    curr_idx := curr_idx + 1;
                end if;
             end loop;   
             
             while not endfile(ref_data_file) loop
                readline(ref_data_file,  ref_line);
             
                -- # is a comment
                if ref_line(1) = '#' then
                    -- Nothing to do it's a comment.
                else
                -- Read the file data into the dut_a_inputs/dut_b_inputs                
                    read(ref_line, ref_data);
                    
                    ref_memory(ref_idx) <= ref_data;
                    ref_idx := ref_idx + 1;
                end if;
             end loop;    
             
             file_last_line_idx <= curr_idx;
             done_read_samples <= '1';
             
             file_close(in_data_file); 
             file_close(ref_data_file); 
             
             is_done := '1';
        end if;
     end if; 
end process;


-- dut_global_start <= '1' after 300 ns, '0' after 500 ns;

-- State Machine Logic
process (CurrState, f_cnt_val, f_cnt_tc, dut_start, dut_config_done,
         dut_out_valid, dut_done, dut_bin_out, dut_out_last, dut_last_frame,
         dut_global_start, mem_cnt_val)
         
    variable tmpA_Val, tmpB_Val: std_logic := '0';
begin
    f_cnt_en <= '0';
    dut_a_data <= '0';
    dut_b_data <= '0';
    -- dut_bin_val <= (others => '1');
    
    dut_start <= '0';
    done_simulate <= '0';
    dut_last_frame <= '0';
    dut_global_start <= '0';
    
    --dut_a_inputs <= (others => '0');
    --dut_b_inputs <= (others => '0');
    --out_data_vector <= (others => '0');
    
    mem_cnt_en <= '0';
    mem_cnt_rst <= '0';
    
    case CurrState is
        when ResetState =>
            -- Open the input file and the output file.
            f_cnt_rst <= '1';
            dut_global_start <= '1';
            NextState <= WaitConfigState;
            
        when IdleState =>
            NextState <= IdleState;
            if dut_global_start='1' then
                NextState <= WaitConfigState;
             end if;
             
        -- TODO: This can be removed now, I think....     
        when WaitConfigState =>
            NextState <= WaitConfigState;
            if dut_config_done='1' then
               -- NextState <= SendDataState;
               NextState <= LoadSampleState;
            end if;
            
        when LoadSampleState => 
            f_cnt_rst <= '1';
            -- If we read the end of the input data file, we are done!
                
           dut_a_inputs <= sample_memory(to_integer(mem_cnt_val))(32 to 63);
           dut_b_inputs <= sample_memory(to_integer(mem_cnt_val))(0 to 31);    
           
           -- wait for 10 ns;    
            dut_start <= '1';    
            NextState <= SendDataState;
        when SendDataState =>
            f_cnt_en <= '1';
            data_in_rdy <= '1';
 --           dut_data_in_rdy <= '1';
            
            tmpA_Val := dut_a_inputs(to_integer(f_cnt_val));
			dut_a_data <= tmpA_Val;

            tmpB_Val := dut_b_inputs(to_integer(f_cnt_val));
			dut_b_data <= tmpB_Val;

            NextState <= SendDataState;
            if f_cnt_tc='1' then
               f_cnt_rst <= '1';
               dut_last_frame <= '1';
               NextState <= SaveOutState;
            end if;      
            
        when SaveOutState =>
            if dut_out_valid='1' then 
                f_cnt_en <= '1';
                out_data_vector(to_integer(f_cnt_val)) <= dut_bin_out;
            end if;
            
            NextState <= SaveOutState;
            if dut_out_last='1' then
                NextState <= SaveFileState;
            end if;
                        
       when SaveFileState =>              
            -- Save the current result out
--            write(out_line, out_data_vector); -- right, out_data_vector'length);
--            writeline(out_data_file, out_line);  
            
--            -- Start Again reading a new input data line.  
--            if endfile(in_data_file) then
--               NextState <= DoneState;
--            else
--               NextState <= LoadCurrLineData;
--            end if;

             out_memory(to_integer(mem_cnt_val)) <= out_data_vector;

             -- Given that we are doing this at the end, the stop condition is 
             -- that mem_cnt_val = file_last_line_idx-1, line count from 1 to N
             -- counter count fro  0.
             if to_integer(mem_cnt_val) = file_last_line_idx-1 then
                NextState <= DoneState;
             else
             -- Load a new sample.
                mem_cnt_en <= '1';
                NextState  <= LoadSampleState;
             end if;

        when DoneState =>
            done_simulate <= '1';
            NextState <= DoneState;
        when others => NextState <= IdleState;
    end case;
end process;

---- This process will write out the out memory into a file
process (Clk, done_simulate)
    variable file_data_line: line;
    variable file_is_open: boolean := false; 
    variable ref_sample: std_logic_vector(0 to NUM_OF_SAMPLES-1);
    variable curr_sample: std_logic_vector(0 to NUM_OF_SAMPLES-1);
begin
    if file_is_open = false then 
        file_open(out_data_file, "W:\\proj_thesis\\hw\\fpga\\fft_vec_by_circ\\tb\\sim\\out_data_32.txt", write_mode);
        file_is_open := true;
    end if;
    
    if (done_simulate='1') then
        -- End of test
        -- Loop through all the element of the out ram not null,
        -- so up until file_last_line_id and write out the result 
        -- in there.
        for idx in 0 to file_last_line_idx-1 loop
             ref_sample := ref_memory(idx);
             curr_sample := out_memory(idx);
        
             write(file_data_line, curr_sample, right, 2**SAMPLES_LOG2);
             write(file_data_line, ' ');
             if (unsigned(ref_sample) = unsigned(curr_sample)) then
                write(file_data_line, string'(" OK"));
             else
                write(file_data_line, string'(" FAIL"));
             end if;
             
             writeline(out_data_file, file_data_line);
        end loop;
        
        file_close(out_data_file);
        
        report "Not a real failure. Simulation finished successfully. Test completed successfully" severity failure;
    end if;
end process;


DUT: entity work.FFT_VecByCirc5_opt port map (
    Clk => Clk, Rst_n => Rst_n,
    start_in => dut_start,
    config_done => dut_config_done,

    a_data      => dut_a_data,
    b_data      => dut_b_data,
    last_frame  => dut_last_frame,
-- NOTE: This would be a good syn interface but I guess it require 
-- some sort of FIFO/RAM at the input?
--    a_data_rdy    => dut_a_data_rdy,
--    a_data_last   => dut_a_last,
--    b_data_rdy    => dut_b_data_rdy,
--    b_data_last   => dut_b_last,
    data_in_rdy   => data_in_rdy,

    data_out      => dut_bin_out,
    data_out_valid  => dut_out_valid,
    data_out_last => dut_out_last
);

process(Rst_n, dut_bin_out)
begin
    if Rst_n='0' then
        dut_bin_val <= (others => '0');
    else
-- If the data outn is avaible read it.
       if dut_bin_out='1' then
           dut_bin_val <= "01";
       else
           dut_bin_val <= "00"; 
       end if;
   end if;
end process;

F_IDX_CNT: Counter generic map(N => SAMPLES_LOG2) 
port map (Clk => Clk, Rst_n => Rst_n, 
          En => f_cnt_en, TC => f_cnt_tc,
          CntValue => f_cnt_val,
          CntRst => f_cnt_rst
);

SAMPLE_IDX_CNT: Counter generic map(N => SAMPLE_FILE_LINES_LOG2) 
port map (Clk => Clk, Rst_n => Rst_n, 
          En => mem_cnt_en, TC => mem_cnt_tc,
          CntValue => mem_cnt_val,
          CntRst => mem_cnt_rst
);

end Behavioral;
