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

entity HASH_BLOCK is
    port(
        CLK         : in std_logic;
        RESET       : in std_logic;
        IN_VLD      : in std_logic;
        HASH_IN     : in std_logic_vector(575 downto 0);
        USED        : out std_logic;
        DATA_OUT    : out std_logic_vector(511 downto 0);
        DATA_VLD    : out std_logic
    );
 end HASH_BLOCK;

 architecture FULL of HASH_BLOCK is

    type state_lane is array (63 downto 0) of std_logic;    
    type state_sheet is array (4 downto 0) of state_lane;    
    type state_array is array (4 downto 0) of state_sheet;

    signal state_array_3d : state_array;

    signal round_count : std_logic_vector(4 downto 0);
    signal round_in    : std_logic_vector(1599 downto 0);
    signal round_out   : std_logic_vector(1599 downto 0) := (others => '0');
    signal reg_data    : std_logic_vector(1599 downto 0) := (others => '0');

    signal const : std_logic_vector(63 downto 0);

    signal accept        : std_logic;
    signal calculate     : std_logic;
    signal counting_done : std_logic;

    signal hash_out : std_logic_vector(1599 downto 0);
    signal hash_not_reorder : std_logic_vector(511 downto 0);

    -- r1
    signal theta_in, rho_in, pi_in, chi_in, iota_in : state_array;
    signal theta_out, rho_out, pi_out, chi_out, iota_out : state_array;
    signal c, d : state_sheet;

begin

    -- accept data if data are valid and not calculating
    accept <= IN_VLD and not(calculate);

    -- used input data
    USED <= accept;

    -- counter
    counter_p : process(CLK)
    begin
        if (rising_edge(CLK)) then
            if (RESET = '1') then
                round_count <= (others => '0'); 
                counting_done <= '0';
            elsif (round_count = "10111") then
                round_count <= (others => '0');
                counting_done <= '0';
            else
                if (round_count = "10110") then
                    counting_done <= '1';
                end if;
                if (accept = '1' or calculate = '1') then
                    round_count <= std_logic_vector(unsigned(round_count) + 1);
                end if;
            end if;
        end if;
    end process;

    -- calculate
    calculate_p : process(CLK)
    begin
        if (rising_edge(CLK)) then
            if (RESET = '1') then
                calculate <= '0'; 
            else
                calculate <= (calculate and not(counting_done)) or accept;
            end if;
        end if;
    end process;

    -- capacity
    capacity : for x in 0 to 1023 generate
        round_in(x) <= reg_data(x);
    end generate;

    -- data in 
    data_i : for x in 0 to 575 generate
        round_in(1599-x) <= (((reg_data(1599-x) xor HASH_IN(575-x)) and accept) or (reg_data(1599-x) and not(accept)));
    end generate;

    -- out valid
    data_vld_p : process(CLK)
    begin
        if (rising_edge(CLK)) then
            if (RESET = '1' or accept = '1') then
                DATA_VLD <= '0'; 
            elsif (counting_done = '1') then
                DATA_VLD <= '1';
            else
                DATA_VLD <= '0';
            end if;
        end if;
    end process;

    -- select const based on counter value
    round_constant1 : process(round_count)
    begin
        case round_count is
            when "00000" => const <= X"0000000000000001"; --1
            when "00001" => const <= X"0000000000008082"; --2
            when "00010" => const <= X"800000000000808A"; --3
            when "00011" => const <= X"8000000080008000"; --4
            when "00100" => const <= X"000000000000808B"; --5
            when "00101" => const <= X"0000000080000001"; --6
            when "00110" => const <= X"8000000080008081"; --7
            when "00111" => const <= X"8000000000008009"; --8
            when "01000" => const <= X"000000000000008A"; --9
            when "01001" => const <= X"0000000000000088"; --10
            when "01010" => const <= X"0000000080008009"; --11
            when "01011" => const <= X"000000008000000A"; --12
            when "01100" => const <= X"000000008000808B"; --13
            when "01101" => const <= X"800000000000008B"; --14
            when "01110" => const <= X"8000000000008089"; --15
            when "01111" => const <= X"8000000000008003"; --16
            when "10000" => const <= X"8000000000008002"; --17
            when "10001" => const <= X"8000000000000080"; --18
            when "10010" => const <= X"000000000000800A"; --19
            when "10011" => const <= X"800000008000000A"; --20
            when "10100" => const <= X"8000000080008081"; --21
            when "10101" => const <= X"8000000000008080"; --22
            when "10110" => const <= X"0000000080000001"; --23
            when "10111" => const <= X"8000000080008008"; --24
            when others  => const <= (others => '0');
        end case;
    end process;

    --------------------------------------------------------------------------------------------------------------------
    -- PERMUTATIONS ----------------------------------------------------------------------------------------------------
    --------------------------------------------------------------------------------------------------------------------

    -- define 3D array
    gen_3d_arr_1 : for x in 0 to 4 generate
        gen_3d_arr_2 : for y in 0 to 4 generate
            gen_3d_arr_3 : for z in 0 to 63 generate
                state_array_3d(y)(x)(63-z) <= round_in(1599-64*(5*x+y)-z);
            end generate;
        end generate;
    end generate;

    -- ROUND 1 --------------------------------------------------------------------------------------------------------

    theta_in <= state_array_3d;
    rho_in <= theta_out;
    pi_in <= rho_out;
    chi_in <= pi_out;
    iota_in <= chi_out;

    -- theta
    theta_00_g : for x in 0 to 4 generate
        theta_01_g : for w in 63 downto 0 generate
            c(x)(w) <= theta_in(x)(0)(w) xor theta_in(x)(1)(w) xor theta_in(x)(2)(w) xor theta_in(x)(3)(w) xor theta_in(x)(4)(w);
        end generate;
    end generate;

    theta_10_g : for x in 0 to 4 generate
        theta_11_g : for w in 0 to 63 generate
            d(x)(w) <= c((x-1) mod 5)(w) xor c((x+1) mod 5)((w-1) mod 64);
        end generate;
    end generate;

    theta_30_g : for x in 0 to 4 generate
        theta_31_g : for y in 0 to 4 generate
            theta_32_g : for w in 0 to 63 generate
                theta_out(x)(y)(w) <= theta_in(x)(y)(w) xor d(x)(w);
            end generate;
        end generate;
    end generate;

    -- rho 
    rho_out(0)(0) <= rho_in(0)(0);
    rho_out(1)(0) <= rho_in(1)(0)(62 downto 0)    & rho_in(1)(0)(63);
    rho_out(2)(0) <= rho_in(2)(0)(63-62 downto 0) & rho_in(2)(0)(63 downto 63-62+1);
    rho_out(3)(0) <= rho_in(3)(0)(63-28 downto 0) & rho_in(3)(0)(63 downto 63-28+1);
    rho_out(4)(0) <= rho_in(4)(0)(63-27 downto 0) & rho_in(4)(0)(63 downto 63-27+1);

    rho_out(0)(1) <= rho_in(0)(1)(63-36 downto 0) & rho_in(0)(1)(63 downto 63-36+1);
    rho_out(1)(1) <= rho_in(1)(1)(63-44 downto 0) & rho_in(1)(1)(63 downto 63-44+1);
    rho_out(2)(1) <= rho_in(2)(1)(63-6 downto 0)  & rho_in(2)(1)(63 downto 63-6+1);
    rho_out(3)(1) <= rho_in(3)(1)(63-55 downto 0) & rho_in(3)(1)(63 downto 63-55+1);
    rho_out(4)(1) <= rho_in(4)(1)(63-20 downto 0) & rho_in(4)(1)(63 downto 63-20+1);

    rho_out(0)(2) <= rho_in(0)(2)(63-3 downto 0)  & rho_in(0)(2)(63 downto 63-3+1);
    rho_out(1)(2) <= rho_in(1)(2)(63-10 downto 0) & rho_in(1)(2)(63 downto 63-10+1);
    rho_out(2)(2) <= rho_in(2)(2)(63-43 downto 0) & rho_in(2)(2)(63 downto 63-43+1);
    rho_out(3)(2) <= rho_in(3)(2)(63-25 downto 0) & rho_in(3)(2)(63 downto 63-25+1);
    rho_out(4)(2) <= rho_in(4)(2)(63-39 downto 0) & rho_in(4)(2)(63 downto 63-39+1);

    rho_out(0)(3) <= rho_in(0)(3)(63-41 downto 0) & rho_in(0)(3)(63 downto 63-41+1);
    rho_out(1)(3) <= rho_in(1)(3)(63-45 downto 0) & rho_in(1)(3)(63 downto 63-45+1);
    rho_out(2)(3) <= rho_in(2)(3)(63-15 downto 0) & rho_in(2)(3)(63 downto 63-15+1);
    rho_out(3)(3) <= rho_in(3)(3)(63-21 downto 0) & rho_in(3)(3)(63 downto 63-21+1);
    rho_out(4)(3) <= rho_in(4)(3)(63-8 downto 0)  & rho_in(4)(3)(63 downto 63-8+1);

    rho_out(0)(4) <= rho_in(0)(4)(63-18 downto 0) & rho_in(0)(4)(63 downto 63-18+1);
    rho_out(1)(4) <= rho_in(1)(4)(63-2 downto 0)  & rho_in(1)(4)(63 downto 63-2+1);
    rho_out(2)(4) <= rho_in(2)(4)(63-61 downto 0) & rho_in(2)(4)(63 downto 63-61+1);
    rho_out(3)(4) <= rho_in(3)(4)(63-56 downto 0) & rho_in(3)(4)(63 downto 63-56+1);
    rho_out(4)(4) <= rho_in(4)(4)(63-14 downto 0) & rho_in(4)(4)(63 downto 63-14+1);

    -- pi
    pi_0_g: for x in 0 to 4 generate
        pi_1_g: for y in 0 to 4 generate
            pi_2_g: for w in 0 to 63 generate
                pi_out(x)(y)(w) <= pi_in((x + 3*y) mod 5)(x)(w);
            end generate;	
        end generate;
    end generate;

    --chi
    chi_0_g: for x in 0 to 4 generate
        chi_1_g: for y in 0 to 4 generate
            chi_2_g: for w in 0 to 63 generate
                chi_out(x)(y)(w) <= chi_in(x)(y)(w) xor ((chi_in((x+1) mod 5)(y)(w) xor '1') and chi_in((x+2) mod 5)(y)(w));
            end generate;	
        end generate;
    end generate;

    -- iota
    iota_00_g: for y in 1 to 4 generate
        iota_01_g: for x in 0 to 4 generate
            iota_02_g: for i in 0 to 63 generate
                iota_out(y)(x)(i)<=iota_in(y)(x)(i);
            end generate;	
        end generate;
    end generate;

    iota_10_g: for x in 1 to 4 generate
        iota_11_g: for i in 0 to 63 generate
            iota_out(0)(x)(i)<=iota_in(0)(x)(i);
        end generate;	
    end generate;

    iota_20_g: for i in 0 to 63 generate
        iota_out(0)(0)(i)<=iota_in(0)(0)(i) xor const(i);
    end generate;

    -- OUT TO VECTOR ---------------------------------------------------------------------------------------------------

    to_vect_p : process(CLK)
    begin
        if (rising_edge(CLK)) then
            if(RESET = '1') then
                reg_data <= (others => '0');
                hash_out <= (others => '0');
            end if;
            if ((calculate or accept) = '1') then
                for x in 0 to 4 loop
                    for y in 0 to 4 loop
                        for z in 0 to 63 loop
                            reg_data(1599-64*(5*x+y)-z) <= iota_out(y)(x)(63-z);
                        end loop;
                    end loop;
                end loop;
            end if;
            if (counting_done = '1') then
                for x in 0 to 4 loop
                    for y in 0 to 4 loop
                        for z in 0 to 63 loop
                            hash_out(1599-64*(5*x+y)-z) <= iota_out(y)(x)(63-z);
                        end loop;
                    end loop;
                end loop;
            end if;
        end if;
    end process;

    -- assign not reordered hash
    hash_not_reorder <= hash_out(1599 downto 1088);

    -- reoder hash
    hash_reorder_g1 : for w in 0 to 7 generate
        hash_reorder_g2 : for b in 0 to 7 generate 
            DATA_OUT(((w*64+b*8)+7) downto (w*64+b*8)) <= hash_not_reorder(((w*64+(7-b)*8)+7) downto (w*64+(7-b)*8));
        end generate;
    end generate;

end FULL;