library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity SPI is
      Port (  
    
           clk                        : in    STD_LOGIC := '0';                    --clock signal
           xce                        : out   STD_LOGIC := '0';                    --active communication signalization  
           sck                        : out   STD_LOGIC := '0';                    --SPI clock signal   
           sdi                        : in    STD_LOGIC := '0';                    --input signal from sensor
           sdo                        : out   STD_LOGIC := '0';                    --output signal to sensor
           allowed                    : in    STD_LOGIC := '0';                    --is communication interval allowed?
           interrupt_speed            : in    STD_LOGIC_VECTOR(20 downto 0) := (others => '0'); --how many clocks will interrupt last?
           chip_id                    : in    STD_LOGIC_VECTOR(7 downto 0) := (others => '0');  --chip address
           start_address              : in    STD_LOGIC_VECTOR(7 downto 0) := (others => '0');  --register address
           data_in                    : out   STD_LOGIC_VECTOR(7 downto 0) := (others => '0');  --input data from sensor
           data_out                   : in    STD_LOGIC_VECTOR(7 downto 0) := (others => '0');  --output data to sensor
           data_valid                 : in    STD_LOGIC := '0';                    --is communication with processor completed?
           communication_ok           : out   STD_LOGIC := '0';                    --did communication completed without interrupt?
           communication_ok_axi       : out   STD_LOGIC := '0';
           xce_en                     : in    STD_LOGIC := '0';
           want_to_communicate        : in    std_logic := '0';                    --processor asks for interrupt output
           writing                    : in    STD_LOGIC := '0'                     --writing 1, reading 0
           );                   
end SPI;

architecture Behavioral of SPI is

begin
    process(clk)
    
        variable xce_var                : std_logic                   := '1';     --temp variable of xce
        variable sck_var                : std_logic                   := '1';     --temp variable of sck
        variable sdi_var                : std_logic                   := '1';     --temp variable of sdi
        variable sdo_var                : std_logic                   := '1';     --temp variable of sdo
        variable communication_ok_var   : std_logic                   := '0';     --temp variable of communication_ok
        variable data_valid_var         : std_logic                   := '0';     --temp variable of data_valid
        variable writing_var            : std_logic                   := '0';     --temp variable of writing
        variable bit_counter            : integer range 0 to 18       := 0;       --bit counter (transfer by 3x8 bits)
        CONSTANT DATA_WIDTH             : integer                     := 8;       --bit count of one address/data
        variable chip_id_last           : std_logic_vector(7 downto 0)  := "00000000";
        variable data_out_last          : std_logic_vector(7 downto 0)  := "00000000";
        variable start_address_last     : std_logic_vector(7 downto 0)  := "00000000";
        variable interrupt_speed_var    : integer range 0 to 1020       := 100;
        variable data_in_var            : std_logic_vector(7 downto 0)  := "00000000";
        
        --communication state machine
        type typ_dat is (
            idle_state,             --ready for communication
            just_confirm,            --for repeated transfers
            end_of_communication,   --communication successful
            chip_id_state,          --writing of chip id
            start_address_state,    --writing of address
            data_state,             --writing(and reading) of data
            interrupt_delay        --adds delay to communication_ok
           
            
        ); 
        variable typ_dat_var : typ_dat;
        
    begin
        if(rising_edge(clk))then
            if(xce_en = '0')then
            xce <= '0';
            
            --if communication is allowed
            elsif(allowed = '1')then
                --want to write?
                if(writing = '1')then
                    --writing_sig <= '1';
                    case typ_dat_var is
                        when idle_state             =>  xce_var := '0';
                                                        bit_counter := 7;
                                                        if(want_to_communicate = '1')then
                                                            communication_ok_var := '0';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;
                                                        
                                                        chip_id_last        := chip_id;
                                                        start_address_last  := start_address;
                                                        data_out_last       := data_out;
                                                        
 
                                                        
                        when chip_id_state          =>  
                                                        
                                                        sdo_var := chip_id_last(bit_counter);
                                                        xce_var := '0';
                                                        if(sck_var = '1')then
                                                            sck_var := '0';
                                                        else
                                                            sck_var := '1';
                                                        end if;
                                                        
                                                        chip_id_last        := chip_id;
                                                        start_address_last  := start_address;
                                                        data_out_last       := data_out;

                                                        
                        when start_address_state    =>  sdo_var := start_address_last(bit_counter);
                                                        xce_var := '0';
                                                        interrupt_speed_var := to_integer(unsigned(interrupt_speed));
                                                        if(sck_var = '1')then
                                                            sck_var := '0';
                                                        else
                                                            sck_var := '1';
                                                        end if;
                                                        
                                                        chip_id_last        := chip_id;
                                                        start_address_last  := start_address;
                                                        data_out_last       := data_out;
                                                        
                                                        
                        when data_state             =>  sdo_var := data_out_last(bit_counter);
                                                        xce_var := '0';
                                                        if(sck_var = '1')then
                                                            sck_var := '0';
                                                        else
                                                            sck_var := '1';
                                                        end if;

                                                        
                                                        
                        when end_of_communication   =>  sdo_var := '1';
                                                        xce_var := '1';
                                                        if(want_to_communicate = '1')then
                                                            communication_ok_var := '1';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;
                                                        interrupt_speed_var := to_integer(unsigned(interrupt_speed));

                        
                        when just_confirm               =>  sdo_var := '1';
                                                        xce_var := '1';
                                                        if(want_to_communicate = '1')then
                                                            communication_ok_var := '0';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;
                                                        
                                                        
                                                        
                        when interrupt_delay        =>  if(want_to_communicate = '1')then
                                                            communication_ok_var := '0';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;
                                                        
                                                        sdo_var := '1';
                                                        xce_var := '1';
                                                                                                                
                        
                    end case;
                --Wants to read?
                else
                    case typ_dat_var is
                        when idle_state             =>  xce_var := '0';
                                                        bit_counter := 7;
                                                        if(want_to_communicate = '1')then
                                                            communication_ok_var := '0';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;
  
                                                        
                        when chip_id_state          =>  chip_id_last        := chip_id;
                                                        start_address_last  := start_address;
                                                        data_out_last       := data_out;
                                                        
                                                        sdo_var := chip_id(bit_counter);
                                                        xce_var := '0';
                                                        if(sck_var = '1')then
                                                            sck_var := '0';
                                                        else
                                                            sck_var := '1';
                                                        end if;
                                                        
                                                        
                                                        
                        when start_address_state    =>  sdo_var := start_address(bit_counter);
                                                        interrupt_speed_var := to_integer(unsigned(interrupt_speed));
                                                        xce_var := '0';
                                                        if(sck_var = '1')then
                                                            sck_var := '0';
                                                        else
                                                            sck_var := '1';
                                                        end if;
                                                        
                                                        
                        when data_state             =>  data_in_var(bit_counter) := sdi_var;
                                                        sdo_var := '0';
                                                        xce_var := '0';
                                                        if(sck_var = '1')then
                                                            sck_var := '0';
                                                        else
                                                            sck_var := '1';
                                                        end if;
                                                        
                                                        
                        when end_of_communication   =>  sdo_var := '1';
                                                        xce_var := '1';
                                                        if(want_to_communicate = '1')then
                                                            communication_ok_var := '1';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;
                                                        interrupt_speed_var := to_integer(unsigned(interrupt_speed));
                                                        
                        when just_confirm           =>  sdo_var := '1';
                                                        xce_var := '1';
                                                        if(want_to_communicate = '1')then
                                                            communication_ok_var := '0';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;

                                                        
                        when interrupt_delay        =>  if(want_to_communicate = '1')then
                                                            communication_ok_var := '0';
                                                        else
                                                            communication_ok_var := '0';
                                                        end if;
                                                        
                                                        sdo_var := '1';
                                                        xce_var := '1';

                        
                    end case;
                end if; --writing
                    if(sck_var = '1')then
                        if(bit_counter < data_width-1)then
                            bit_counter := bit_counter + 1;
                        else
                            bit_counter := 0;
                            case typ_dat_var is
                                when idle_state             => typ_dat_var          := chip_id_state;
                                when chip_id_state          => typ_dat_var          := start_address_state;
                                when start_address_state    => typ_dat_var          := data_state;
                                when data_state             => typ_dat_var          := interrupt_delay;
                                                               interrupt_speed_var  := interrupt_speed_var - 1;
                                                               
                                when interrupt_delay        =>  if(interrupt_speed_var <= 0)then
                                                                    typ_dat_var := end_of_communication;
                                                                else
                                                                    interrupt_speed_var := interrupt_speed_var - 1;
                                                                end if;
                                                               
                                when end_of_communication   =>  
                                                                if(chip_id_last = chip_id   AND start_address_last = start_address  AND data_out_last = data_out   AND data_valid_var = '1')then
                                                                    typ_dat_var := end_of_communication;
                                                                end if;
                                                                if((chip_id_last /= chip_id OR start_address_last /= start_address  OR data_out_last /= data_out)  AND data_valid_var = '1')then
                                                                    typ_dat_var := idle_state;
                                                                end if;
                                                                if((chip_id_last = chip_id  AND start_address_last = start_address  AND data_out_last = data_out)  AND data_valid_var = '0')then
                                                                    typ_dat_var := just_confirm;
                                                                end if;
                                when just_confirm               =>  
                                                                if(interrupt_speed_var <= 0)then
                                                                    if((chip_id_last = chip_id  AND start_address_last = start_address AND data_out_last = data_out) AND data_valid_var = '1')then
                                                                        typ_dat_var := end_of_communication; 
                                                                    end if;     
                                                                    if((chip_id_last /= chip_id OR start_address_last /= start_address OR data_out_last /= data_out) AND data_valid_var = '1')then                          
                                                                        typ_dat_var := idle_state;
                                                                    end if;
                                                                else
                                                                    interrupt_speed_var := interrupt_speed_var - 1;
                                                                end if;
                            end case;
                        end if;
                    end if;     
            else --allowed
                if(typ_dat_var = idle_state OR typ_dat_var = data_state OR typ_dat_var = start_address_state OR typ_dat_var = chip_id_state)then
                    typ_dat_var := chip_id_state;
                else
                   
                end if;
                sdo_var := '1';
                sdi_var := '1';
                sck_var := '1';
                xce_var := '1';
            end if; --allowed
            data_valid_var        := data_valid;
            communication_ok      <= communication_ok_var;
            communication_ok_axi  <= communication_ok_var;
            data_in               <= data_in_var;
            sck                   <= sck_var;
            sdo                   <= sdo_var;
            xce                   <= xce_var;
            sdi_var               := sdi;
        end if;
    end process;
end Behavioral;