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

-- Only used for Compile time operation, this will be
-- synthetizable 
use ieee.math_real.all;

library work;
use work.Utils.all;


entity APadder is 
generic (
-- TODO: This must be a power of two.
	constant Nb: integer := 4;
	constant P_VAL: integer := 11
);
port (
	Clk, Rst_n: in  std_logic;
	
	start_in: in std_logic; -- Start the sequence of bit

	out_bin: out std_logic;
	out_last: out std_logic;
	out_valid: out std_logic;
-- When one the circuit stop the output sequence.
	out_stop: in std_logic; 

    done: out std_logic;
	
-- 	Position memory
	mem_in_en:   out std_logic; -- Enable signal for the memory
	mem_in_adr:  out std_logic_vector(my_log2(div_ceil(P_VAL, Nb))-1 downto 0);
	mem_in_data: in  std_logic_vector(0 to Nb-1)
);
end APadder;

architecture Behavioral of APadder is
   constant NB_LOG2: integer := my_log2(Nb);


-- Constant depending only on the generic passed to the module! If VHDL compiler is trivial
-- these are computed at compile! And it have to be to be able to syntethize the design. 
   constant P_VAL_EXT: integer := next_pow2(2*P_VAL - 3)-1;
   constant P_VAL_EXT_LOG2: integer := my_log2(P_VAL_EXT);
   
-- This is used by the counters, so if we wanna count P_VAL, we need the range 0 to P_VAL-1
	constant P_VAL_INT: integer := P_VAL - 1;
	constant PAD_BITS:  integer := P_VAL_EXT - P_VAL_INT;
	
-- This are the constant threshold value used to change the FSM state and keep track
-- on what the output bit index is.
	constant LAST_PAD_BIT_POS: integer := PAD_BITS-1;

	constant MEM_ROW_COUNT: integer := div_ceil(P_VAL, Nb);
	constant MEM_ROW_COUNT_LOG2: integer := my_log2(MEM_ROW_COUNT);


    
-- 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;
     
-- For debug purposes
    component RegN is
    generic (N: integer := 16);
    port
    (
        CLK:     in     std_logic;
        Rst_n:   in     std_logic;
        En:      in     std_logic;
        D:       in     std_logic_vector(N-1 downto 0);
        Q:       out std_logic_vector(N-1 downto 0)
    );
    end component;
    
--    component MuxN_PS is 
--    generic (
--        constant N: integer :=  16
--    );
--    port (
--        Rst_n: in std_logic; 
--        Sel: in unsigned(my_log2(N)-1 downto 0);
--        InP: in std_logic_vector(N-1 downto 0);
--        OutS: out std_logic
--    );
--    end component;

-- Signals
type FSM_State is (ResetState, IdleState, BeginFirstRowState, EndFirstRowState, 
        EmitPadBitState,  EmitRowBitState, DoneState);
signal CurrState, NextState: FSM_State;

-- Bit Counter
signal b_cnt_en: std_logic;
signal b_cnt_tc: std_logic;
signal b_cnt_rst: std_logic;
signal b_cnt_val: unsigned(P_VAL_EXT_LOG2-1 downto 0);

-- Row bit counter
signal r_cnt_en: std_logic;
signal r_cnt_tc: std_logic;
signal r_cnt_rst: std_logic;
signal r_cnt_val: unsigned(NB_LOG2-1 downto 0);

-- Memory Address Counter
signal m_cnt_en: std_logic;
signal m_cnt_tc: std_logic;
signal m_cnt_rst: std_logic;
signal m_cnt_val: unsigned(MEM_ROW_COUNT_LOG2-1 downto 0);


-- DP Signals

-- Thsi is one when the current bit the the last bit of a frame
signal dp_out_bin:   std_logic;
signal dp_pad_sel:   std_logic;
signal dp_mux_out:   std_logic;
signal dp_idx_match: std_logic;

-- FSM

signal fsm_done: std_logic;
signal fsm_mem_en: std_logic;
signal fsm_out_last: std_logic;
signal fsm_out_valid: std_logic;

begin

StateUpdate: process(Clk, Rst_n)
begin	
	if Rst_n='0' then 
		CurrState <= ResetState;
	elsif rising_edge(Clk) then
		CurrState <= NextState;
	end if;
end process;

-- NOTE: Maybe implement the muxes with a subtype so it's more readable? 
-- @BeforeShip
-- TODO: Can we do the out outside of the FSM??? 

FSM_Update: process(CurrState, start_in, out_stop, r_cnt_tc, b_cnt_tc,
        dp_idx_match)
begin
    fsm_out_last <= '0';
    fsm_out_valid <= '0';
    
    m_cnt_en <= '0';    
    r_cnt_en <= '0';    
	b_cnt_en <= '0';
		
	b_cnt_rst <= '0';    
    m_cnt_rst <= '0';    
    r_cnt_rst <= '0'; 
    fsm_mem_en <= '0';
-- Default to one, so we know it will be always one when we reset the system	
	dp_pad_sel <= '1';
	
	case CurrState is
		when ResetState => 
			b_cnt_en <= '0';
			
			b_cnt_rst <= '1';    
			m_cnt_rst <= '1';	
			r_cnt_rst <= '1';	
			
			m_cnt_en <= '0';    
			r_cnt_en <= '0';    
			b_cnt_en <= '0';
			
-- Default to one, so we know it will be always one when we reset the system	
			dp_pad_sel <= '1';

			fsm_out_last <= '0';
			fsm_out_valid <= '0';
			
			fsm_mem_en <= '0';
			
			fsm_done <= '0';
			
			NextState <= IdleState;
		when IdleState =>
			NextState <= IdleState;
			if start_in='1' then 
			    fsm_done <= '0';
				fsm_mem_en <= '1'; 
				NextState <= BeginFirstRowState;
			end if;
		when BeginFirstRowState =>
		    fsm_mem_en <= '1';
		    
			-- Increment by one the the row bit counter.
			r_cnt_en <= '1';
			dp_pad_sel <= '0';
			fsm_out_valid <= '1';
			
-- Enable the total bit counter and handle the out stop.
			b_cnt_en <= '1';
			if (out_stop = '1') then 
				m_cnt_en <= '0';    
				r_cnt_en <= '0';    
				b_cnt_en <= '0';
			end if;
					
			NextState <= EmitPadBitState;
		when EmitPadBitState =>
		    -- fsm_mem_en <= '1';
		
			r_cnt_en <= '0';
			m_cnt_en <= '0';
			dp_pad_sel <= '1'; -- Emit 0
			fsm_out_valid <= '1';
			
			-- Enable the total bit counter and handle the out stop.
			b_cnt_en <= '1';
			if (out_stop = '1') then 
				m_cnt_en <= '0';    
				r_cnt_en <= '0';    
				b_cnt_en <= '0';
			end if;
						
			NextState <= EmitPadBitState;
			if (dp_idx_match='1') then
			     NextState <= EndFirstRowState; --  EmitRowBitState;
			end if;
			
		--  NOTE: I tried to merge this state with the next one but there where some timing 
		-- problems that to be solve needed to have ugly previous state. Adding a new state
		-- seem be the best solution in both read ability and code cleaness.   	
		when EndFirstRowState =>
                            
         -- We could pass into a middle state to avoid this?
            r_cnt_en <= '1';
            b_cnt_en <= '1';
            fsm_mem_en <= '1';
            dp_pad_sel <= '0';
            fsm_out_valid <= '1';
            
            -- Done counting all bits, read a new row.
            NextState <= EndFirstRowState;
            if (r_cnt_tc = '1') then
                m_cnt_en <= '1';
                NextState <= EmitRowBitState;
            end if;

		when EmitRowBitState =>
			fsm_mem_en <= '1';
			
			r_cnt_en <= '1';
			dp_pad_sel <= '0';
			fsm_out_valid <= '1';
			
			-- Enable the total bit counter and handle the out stop.
			b_cnt_en <= '1';
			if (out_stop = '1') then 
				m_cnt_en <= '0';    
				r_cnt_en <= '0';    
				b_cnt_en <= '0';
			end if;
			
			-- Done counting all bits, read a new row.
			if (r_cnt_tc = '1') then
				m_cnt_en <= '1';
			end if;
						
			NextState <= EmitRowBitState;
			if (b_cnt_tc = '1') then
				fsm_out_last <= '1';
				NextState <= DoneState;
			end if;
		when DoneState =>   
		    fsm_done <= '1';
		    NextState <= IdleState;
		when others =>
		  NextState <= IdleState;	
	end case;
end process;


-- Datapath

-- out_bin <= dp_mem_idx_match when fsm_out_valid='1' else '0';
--BIT_MUX: MuxN_PS generic map (N => Nb) port map (
--	Rst_n => Rst_n, 
--	InP => mem_in_data, 
--	OutS => dp_mux_out,
--	Sel  => r_cnt_val
--);

dp_mux_out <= '0' when Rst_n='0' else
               mem_in_data(to_integer(r_cnt_val));

dp_out_bin <= dp_mux_out when dp_pad_sel = '0' else 
			  '0';
			  
dp_idx_match <= '1' when b_cnt_val > LAST_PAD_BIT_POS else 
				'0';

R_IDX_CNT: Counter generic map(N => NB_LOG2) 
port map (
	Clk => Clk, Rst_n => Rst_n, 
    En => r_cnt_en, TC => r_cnt_tc,
    CntValue => r_cnt_val,
    CntRst   => r_cnt_rst
);

B_IDX_CNT: Counter generic map(N => P_VAL_EXT_LOG2) 
port map (
	Clk => Clk, Rst_n => Rst_n, 
    En => b_cnt_en, TC => b_cnt_tc,
    CntValue => b_cnt_val,
    CntRst   => b_cnt_rst
);

M_IDX_CNT: Counter generic map(N => MEM_ROW_COUNT_LOG2) 
port map (
	Clk => Clk, Rst_n => Rst_n, 
    En  => m_cnt_en, TC => m_cnt_tc,
    CntValue => m_cnt_val,
    CntRst   => m_cnt_rst
);


-- Assign the entity output signals
done <= fsm_done;
out_bin <= dp_out_bin; 
out_last  <= fsm_out_last;
out_valid  <= fsm_out_valid;

mem_in_en <= fsm_mem_en;
mem_in_adr <= std_logic_vector(m_cnt_val);

end Behavioral;



