library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.all;

library work;
use work.Utils.all;

library ieee;
use std.textio.all;
use ieee.std_logic_textio.all;

-- last num, l, is the 2^l max possible input p.
entity tb_vbc14_2ip is
generic (
    constant P_VAL: integer := 14717;
    constant D_V: integer := 15;
    constant Nb:  integer := 16
);
end tb_vbc14_2ip;

architecture Behavioral of tb_vbc14_2ip is

-- Components
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;


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

signal clk, Rst_n: std_logic := '0';

constant P_VAL_LOG2: integer := my_log2(P_VAL);

constant MSG_MEM_ROW_COUNT: integer := div_ceil(P_VAL, Nb);
constant MSG_MEM_ROW_COUNT_LOG2: integer := my_log2(MSG_MEM_ROW_COUNT);

constant POS_MEM_COUNT: integer := D_V;
constant POS_MEM_COUNT_LOG2: integer := my_log2(POS_MEM_COUNT);

-- TB memory file paths
constant TB_MSG_MEM_FILE_PATH: string := "W:\\proj_thesis\\hw\\sim\\data\\vbc\\in\\" & integer'image(P_VAL) & "\\"  & integer'image(Nb) & "\\msg_mem.txt";
constant TB_POS_MEM_FILE_PATH: string := "W:\\proj_thesis\\hw\\sim\\data\\vbc\\in\\" & integer'image(P_VAL) & "\\"  & integer'image(Nb) & "\\pos_mem_" & integer'image(D_V) & ".txt";
constant TB_OUT_MEM_FILE_PATH: string := "W:\\proj_thesis\\hw\\sim\\data\\vbc\\out\\" & integer'image(P_VAL) & "\\" & integer'image(Nb) & "\\tb_mem_"  & integer'image(D_V) & "_2ip.txt";

constant TB_SYSTEM_INFO_STRING: string := "# " & integer'image(P_VAL) &  " " & integer'image(Nb) & " " & integer'image(D_V);

type FSM_State is (Reset, Idle, WaitConfig, StartState, DoState, StreamOut, DoneState);
signal CurrState, NextState: FSM_State := Reset; 

-- This memory contains the position of the bit to 1, we also extend this the enarest power of two to D_V
constant D_V_NEXT_POW_2: integer := next_pow2(D_V);

type msg_mem_type is array(0 to MSG_MEM_ROW_COUNT-1) of std_logic_vector(0 to Nb-1);
type pos_mem_type is array(0 to D_V_NEXT_POW_2-1) of unsigned(0 to P_VAL_LOG2-1);

signal TB_MEM_MSG: msg_mem_type; 

signal TB_MEM_POS: pos_mem_type;

-- Output memory
signal TB_MEM_OUT: msg_mem_type := (others => (others => '0'));

-- This is year to be sure we use the right for the main mul
constant P_VAL_EXT: integer := next_pow2(2*P_VAL - 3);

signal dut_done: std_logic := '0';

-- FSM states
signal done: std_logic := '0';


-- This is the main DUT start signal
signal main_start: std_logic := '0';

signal dut_start: std_logic := '0';


signal msg_mem_en:   std_logic := '0';
signal msg_mem_adr:  std_logic_vector(MSG_MEM_ROW_COUNT_LOG2-1 downto 0) := (others => '0');
signal msg_mem_data: std_logic_vector(Nb-1 downto 0) := (others => '0');


signal out_mem_en:   std_logic := '0';
signal out_mem_adr:  std_logic_vector(MSG_MEM_ROW_COUNT_LOG2-1 downto 0) := (others => '0');
signal out_mem_data: std_logic_vector(Nb-1 downto 0) := (others => '0');

signal pos_mem_en:   std_logic := '0';
signal pos_mem_adr:  std_logic_vector(POS_MEM_COUNT_LOG2-1 downto 0) := (others => '0');
signal pos_mem_data: std_logic_vector(P_VAL_LOG2-1 downto 0) := (others => '0');

signal out_cnt_en:  std_logic := '0';
signal out_cnt_tc:  std_logic := '0';
signal out_cnt_rst: std_logic := '0';
signal out_cnt_val: unsigned(P_VAL_LOG2-1 downto 0) := (others => '0'); 

-- Data files
file msg_mem_file: text;
file pos_mem_file: text;
file out_mem_file: text;

signal done_read_mem_file: std_logic;
signal TB_START_TIME: time;

begin

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

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

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;


-- Read the configuration file!
-- 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 pos_file_line: line;
    variable mem_file_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  curr_msg_data: std_logic_vector(0 to Nb-1) := (others => '0');
     variable curr_pos_data: std_logic_vector(0 to P_VAL_LOG2-1) := (others => '0');
    
begin
   if Rst_n='0' then
        if is_done='0' then
             file_open(msg_mem_file, TB_MSG_MEM_FILE_PATH, read_mode); 
             file_open(pos_mem_file, TB_POS_MEM_FILE_PATH, read_mode); 
                           
             while not endfile(msg_mem_file) loop
                readline(msg_mem_file,  pos_file_line);
             
                -- # is a comment
                if pos_file_line(1) = '#' then
                    -- Nothing to do it's a comment.
                else
                -- Read the file data into the dut_a_inputs/dut_b_inputs                
                    read(pos_file_line, curr_msg_data);  
                            
                    TB_MEM_MSG(curr_idx) <= curr_msg_data;
                    curr_idx := curr_idx + 1;
                end if;
             end loop;   
             
             while not endfile(pos_mem_file) loop
                readline(pos_mem_file,  pos_file_line);
             
                -- # is a comment
                if pos_file_line(1) = '#' then
                    -- Nothing to do it's a comment.
                else
                -- Read the file data into the dut_a_inputs/dut_b_inputs                
                    read(pos_file_line, curr_pos_data);
                    
                    TB_MEM_POS(ref_idx) <= unsigned(curr_pos_data);
                    ref_idx := ref_idx + 1;
                end if;
             end loop;    
             
            -- file_last_line_idx <= curr_idx;
             done_read_mem_file <= '1';
             
             file_close(msg_mem_file); 
             file_close(pos_mem_file); 
             
             is_done := '1';
        end if;
     end if; 
end process;




-- dut_start_tmp <= '1' after 300 ns, '0' after 500 ns;    
main_start <= '1' after 300 ns, '0' after 300 ns + 2*CLOCK_PERIOD;


-- This state machine is used to control the main mul circuit, 
-- we need to weait for the config to be done after the reset is applied.
process(Clk, Rst_n) 
begin
    if Rst_n='0' then
        CurrState <= Reset;
    elsif rising_edge(Clk) then
        CurrState <= NextState;
    end if;
end process;

UpdateProc: process(CurrState, dut_done, main_start)
begin
    done <= '0';
    
    out_cnt_en  <= '0';
    out_cnt_rst <= '0';
    dut_start <= '0';
    case CurrState is 
        when Reset => 
            dut_start <= '0';
            NextState <= Idle;
        when Idle  => 
            out_cnt_rst <= '1';
            out_cnt_en  <= '0';
            NextState <= Idle;
            if main_start = '1' then
                TB_START_TIME <= now;
                dut_start <= '1';
                NextState <= DoState;
            end if;
        when DoState =>         
            NextState <= DoState;
            if dut_done='1' then
                NextState <= DoneState;
            end if;
        when DoneState =>
            done <= '1';
            NextState <= Idle;
        when others => NextState <= Idle;
    end case;
end process;

SaveProc: process (Rst_n, done)
    variable curr_idx: unsigned(MSG_MEM_ROW_COUNT_LOG2-1 downto 0) := (others => '0');
    variable got_done: std_logic := '0';
    variable SystemInfoLine: line;
    
    variable TotalSimTime: line;
    
    variable curr_mem_data: std_logic_vector(Nb-1 downto 0) := (others => '0');
    variable file_data_line: line;
   
    variable is_done: std_logic := '0';

    variable sim_time: time;
begin
    if Rst_n = '0' then
         is_done := '0';
         curr_mem_data := (others => '0');
         file_open(out_mem_file, TB_OUT_MEM_FILE_PATH, write_mode); 
        
         -- Write system params as first filer line comment
         write(SystemInfoLine, TB_SYSTEM_INFO_STRING);
         writeline(out_mem_file, SystemInfoLine);
            
     elsif done='1' and is_done='0' then    
---Write The total sim time.  
       sim_time := now - TB_START_TIME;
       write(TotalSimTime, "# Sim Time: " & time'image(now - TB_START_TIME));   
       writeline(out_mem_file, TotalSimTime);  
          
       for idx in 0 to MSG_MEM_ROW_COUNT-1 loop
         curr_mem_data := TB_MEM_OUT(idx);
    
           write(file_data_line, curr_mem_data, right, Nb);
--         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_mem_file, file_data_line);
        end loop;
        
        file_close(out_mem_file);
        is_done := '1';
        
 --       wait for 1 us; 
    elsif is_done='1' then
         report "Not a real failure. Simulation finished successfully. Took: " & time'image(sim_time) severity failure;
    end if;
end process;

DUT_COMP: entity work.VecByCircOpt14_2ip_top generic map(
    D_V => D_V, 
    Nb => Nb,
    P_VAL => P_VAL
) port map (
   Clk => Clk, Rst_n => Rst_n,
   start_in => dut_start,
   done => dut_done,
   
  
-- Message Memory
   msg_mem_en   => msg_mem_en,
   msg_mem_adr  => msg_mem_adr,
   msg_mem_data => msg_mem_data,
   
-- Position Memory
   pos_mem_en   => pos_mem_en,
   pos_mem_adr  => pos_mem_adr,
   pos_mem_data => pos_mem_data,
    
-- Out Memory 
   out_mem_en   => out_mem_en,
   out_mem_adr  => out_mem_adr,
   out_mem_data => out_mem_data 
);

-- Just used for debug to count the current bit position.
F_IDX_CNT: Counter generic map(N => P_VAL_LOG2) 
port map (Clk => Clk, Rst_n => Rst_n, 
          En => out_cnt_en, TC => out_cnt_tc,
          CntValue => out_cnt_val,
          CntRst => out_cnt_rst
);

MSG_ROM: process (Rst_n, Clk, msg_mem_en, msg_mem_adr)
begin
   if Rst_n='0' then
--     msg_mem_adr <= (others => '0');
     msg_mem_data <= (others => '0');
   else --if rising_edge(Clk) then
     msg_mem_data <= (others => '0');
     if msg_mem_en = '1' then
         msg_mem_data <= TB_MEM_MSG(to_integer(unsigned(msg_mem_adr)));
     end if;
   end if;
end process; 

POS_ROM: process (Rst_n, Clk, pos_mem_en, pos_mem_adr)
   -- variable  pos_mem_int: integer range 0 to P_VAL := 0;
    variable  pos_mem_un: unsigned(my_log2(P_VAL)-1 downto 0) := (others => '0');
begin
   if Rst_n='0' then
--     pos_mem_adr <= (others => '0');
     pos_mem_data <= (others => '0');
   else --if rising_edge(Clk) then
     pos_mem_data <= (others => '0');
     if pos_mem_en = '1' then
         pos_mem_un := TB_MEM_POS(to_integer(unsigned(pos_mem_adr)));
         -- pos_mem_un  := to_unsigned(pos_mem_int, pos_mem_un'length);
         pos_mem_data <= std_logic_vector(pos_mem_un);
     end if;
   end if;
end process;
 
OUT_ROM: process (Rst_n, Clk, out_mem_en, out_mem_adr)
begin
   if Rst_n='0' then
     TB_MEM_OUT <= (others => (others => '0'));
   else --if rising_edge(Clk) then
     -- dut_b_mem_data <= (others => '0');
     if out_mem_en = '1' then
         TB_MEM_OUT(to_integer(unsigned(out_mem_adr))) <= out_mem_data;
     end if;
   end if;
end process;



end Behavioral;
