LIBRARY ieee;
USE ieee.numeric_std.all;
USE ieee.std_logic_1164.all;

ENTITY spi_slave IS
	GENERIC (
		BYTES		:	positive := 2;								--! Nastavitelny pocet prenasenych bytu
		CLK_FREQ	:	positive := 50_000_000;						--! Frekvence interniho hodinoveho signalu
		SPI_MFREQ	:	positive := 100_000							--! Minimalni frekvence SPI, pro timeout komunikace
	);
	PORT (
		CLK			:	IN std_logic;								--! Interni hodinovy signal hradloveho pole
		RST			:	IN std_logic;								--! Reset pin, aktivni v log. 1

		SCLK		:	IN std_logic;								--! Hodinovy signal SPI
		CS_N		:	IN std_logic;								--! Chip select signal SPI
		MOSI		:	IN std_logic;								--! Master Out Slave In SPI 
		MISO		:	OUT std_logic;								--! Master In Slave Out SPI
		
		DIN			:	IN std_logic_vector((8*BYTES)-1 DOWNTO 0);	--! Vektor vstupnich dat urcenych pro odeslani do Master SPI
		DOUT		:	OUT std_logic_vector((8*BYTES)-1 DOWNTO 0); --! Vektor vystupnich dat prijatych z Master SPI
		DOUT_VLD	:	OUT std_logic; 								--! Prijata data jsou validni, kdyz je na vystupu log. 1
		DOUT_ERR	:	OUT std_logic								--! Behem komunikace nastala chyba, kdyz je na vystupu log. 1
	);
END;
 

ARCHITECTURE Behavioral OF spi_slave IS

	CONSTANT BITS	: positive := (8*BYTES)-1;						--! Prepocet prenasenych bytu na bity
	CONSTANT TOUT	: positive := CLK_FREQ/SPI_MFREQ*(8*BYTES*2+1);	--! Prepocet hodinovy pulzu pro timeout komunikace

	SIGNAL	iSCLK_meta		:	std_logic := '0';					--! SCLK signal v interni hodinove domene
	SIGNAL	iCS_N_meta		:	std_logic := '0';					--! CS_N signal v interni hodinove domene
	SIGNAL	iMOSI_meta		:	std_logic := '0';					--! MOSI signal v interni hodinove domene
	
	SIGNAL	iSCLK_stab		:	std_logic := '0';					--! Hodnota signalu SCLK v predchozim taktu
	SIGNAL	iCS_N_stab		:	std_logic := '0';					--! Hodnota signalu CS_N v predchozim taktu
	SIGNAL	iMOSI_stab		:	std_logic := '0';					--! Hodnota signalu MOSI v predchozim taktu

	SIGNAL	iSCLK_redge		:	std_logic := '0';					--! Paklize je nabezna hrana signalu SCLK, tak je v log. 1
	--SIGNAL	iSCLK_fedge		:	std_logic := '0';					--! Paklize je sestupna hrana signalu SCLK, tak je v log. 1

	--SIGNAL  iCS_N_redge		: 	std_logic := '0';					--! Paklize je nabezna hrana signalu CS_N, tak je v log. 1
	SIGNAL	iCS_N_fedge		:	std_logic := '0';					--! Paklize je sestupna hrana signalu CS_N, tak je v log. 1

	SIGNAL 	bit_cnt			:	natural := 0;						--! Hodnota citace bitu	
	SIGNAL 	last_bit		:	std_logic := '0';					--! Paklize je detekovan posledni bit komunikace, tak je v log. 1

	SIGNAL  iDOUT			:	std_logic_vector(BITS DOWNTO 0) := (OTHERS => '0'); --! Vektor, ve kterem se interne ukladaji prijate bity
	SIGNAL	iDOUT_error		:	std_logic := '0';					--! Behem komunikace nastala chyba, kdyz je na vystupu log. 1
	SIGNAL	iDOUT_valid		:	std_logic := '0';					--! Prijata data jsou validni, kdyz je na vystupu log. 1

	SIGNAL	iDIN			:	std_logic_vector(BITS DOWNTO 0) := (OTHERS => '0'); --! Vektor, ve kterem se interne ukladaji odesilane bity

	SIGNAL  iMISO_error		:	std_logic := '0';					--! Paklize se zarizeni nachazi ve stavu Error, tak je na vystupu log. 1

	TYPE 	t_State 		IS (Idle, Recieve, Error, Succes);		--! Stavovy automat pro prijimani a odesilani
	SIGNAL	State			:	t_State	  := Idle;

BEGIN

	-- synchronizace s nabeznou hranou hodinoveho signalu do hlavni casove domeny

	Time_domain_sync: PROCESS (CLK)
	BEGIN
		IF (rising_edge(CLK)) THEN
			iSCLK_meta <= SCLK;
			iCS_N_meta <= CS_N;
			iMOSI_meta <= MOSI;

			iSCLK_stab <= iSCLK_meta;
			iCS_N_stab <= iCS_N_meta;
			iMOSI_stab <= iMOSI_meta;
		END IF;
	END PROCESS;
	
	-- detekce hran
	
	Edge_detect: PROCESS (CLK)
	BEGIN
		IF (rising_edge(CLK)) THEN
			-- detekce vzestupne hrany SCLK SPI signalu
			iSCLK_redge <= iSCLK_meta AND NOT iSCLK_stab;
			
			--detekce sestupne hrany CS_N SPI signalu
			iCS_N_fedge <= NOT iCS_N_meta AND  iCS_N_stab;			
		END IF;
	END PROCESS;

	-- pocitani bitu

	Bit_count: PROCESS (CLK)
	BEGIN
		IF rising_edge(CLK) THEN
			IF (RST = '1' OR iCS_N_stab = '1') THEN
				bit_cnt <= 0;
			ELSIF (iSCLK_redge = '1') THEN
				bit_cnt <= bit_cnt + 1;
			END IF;
		END IF;
	END PROCESS;

	-- posledni bit komunikace

	last_bit <= '1' WHEN (bit_cnt = BITS + 1 ) ELSE '0'; 

	-- detekce chyby komunikace, kdyz prijde vice bitu, nez je ocekavano, nebo byla predcasne ukoncena komunikace

	iDOUT_error <= '1' WHEN (last_bit ='1' AND iSCLK_redge = '1') ELSE '0';
	
	-- stavovy automat pro prijem dat

	State_machine: PROCESS (CLK)
		VARIABLE cnt_tout : integer := 0;
	BEGIN
		IF rising_edge(CLK) THEN
			IF (RST = '1') THEN
				State <= Idle;
				iDOUT_valid <= '0';
			ELSE
				CASE State IS

					-- cekani na sestupnou hranu signalu CS_N
					WHEN Idle =>
						IF (iCS_N_fedge = '1') THEN
							cnt_tout := 0;
							iDOUT_valid <= '0';
							iDOUT <= (OTHERS => '0');
							iDIN <= DIN;
							State <= Recieve;
						END IF;

					-- prijmani bitu => komunikace	
					WHEN Recieve =>
						-- bylo prijato vice bitu nez je ocekavano, nebo byla ukoncena komunikace
						IF (iDOUT_error = '1' OR cnt_tout = TOUT) THEN
								State <= Error;

						-- komunikace probehla v poradku 
						ELSIF (last_bit = '1' AND iCS_N_stab = '1') THEN
								State <= Succes;
								
						-- probihajici komunikace		
						ELSIF (iSCLK_redge = '1') THEN
							iDOUT(BITS - bit_cnt) <= iMOSI_stab;
							MISO <= iDIN(BITS - bit_cnt);
						END IF;

						cnt_tout := cnt_tout + 1;

					-- chybovy stav, ktery byl vyvolan iDOUT_error, pro reset nutno znovu zahajit komunikaci	
					WHEN Error =>
						IF (RST = '1' OR CS_N = '1') THEN
							iMISO_error <= '0';
							State <= Idle;
						ELSE
							iMISO_error <= '1';
						END IF;

					-- v pripade uspesne komunikace se hodnoty zapisi na vystup
					WHEN Succes =>
						DOUT <= iDOUT;	
						iDOUT_valid <= '1';
						State <= Idle;
				END CASE;
			END IF;
		END IF;
	END PROCESS;

	DOUT_VLD <= iDOUT_valid;
	DOUT_ERR <= iMISO_error;

END Behavioral;