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

entity flashrom_spi is
	generic(
		clk_divider        : integer range 2 to 9999 := 2;
		max_word_length    : integer                 := 32;
		max_dummy_bits     : integer                 := 8;
		cs_inactive_cycles : integer                 := 10
	);
	port(
		clk                 : in  std_logic;
		rst                 : in  std_logic;
		clr                 : in  std_logic;

		-- SPI flash hardware signals
		spi_si              : out std_logic; -- SPI serial in
		spi_so              : in  std_logic; -- SPI serial out
		spi_sck             : out std_logic; -- SPI clock
		spi_cs_n            : out std_logic; -- SPI chip select

		-- Logic interface
		data_in_valid       : in  std_logic; -- Data to Flash ROM
		data_in             : in  std_logic_vector(max_word_length - 1 downto 0);
		data_in_length      : in  integer range 0 to max_word_length;
		data_next           : out std_logic;
		data_out            : out std_logic_vector(max_word_length - 1 downto 0);
		data_out_valid      : out std_logic; -- Data from Flash ROM
		data_out_length     : in  integer range 0 to max_word_length;
		data_out_dummy_bits : in  integer range 0 to max_dummy_bits;
		transmission_active : out std_logic
	);
end entity flashrom_spi;

architecture RTL of flashrom_spi is
	type txstate_t is (IDLE, TX);
	signal state             : txstate_t;
	signal ckDiv             : integer range 0 to clk_divider - 2;
	signal shiftreg          : std_logic_vector(max_word_length - 1 downto 0);
	signal bitCounter        : integer range 0 to max_word_length - 1;
	signal bitCounterIn      : integer range 0 to max_word_length + max_dummy_bits - 1; -- TODO: Actually this must count until the higher of the two
	signal data_in_length_i  : integer range 0 to max_word_length;
	signal data_out_length_i : integer range 0 to max_word_length;
	signal delayCycle        : std_logic;
	signal oneBitRead        : std_logic;
	signal pseudoEdge        : boolean;
	signal dummy_passed      : boolean;
	signal prevent_retrig    : boolean;
	signal cs_inactive_cnt   : integer range 0 to cs_inactive_cycles;
begin
	toSpi : process(clk, rst) is
		procedure default_state is
		begin
			data_next       <= '0';
			data_out_valid  <= '0';
			pseudoEdge      <= false;
			cs_inactive_cnt <= 0;
		end procedure default_state;

		procedure reset_state is
		begin
			state        <= IDLE;
			spi_sck      <= '0';
			shiftreg     <= (others => '0');
			bitCounter   <= 0;
			bitCounterIn <= 0;
			data_out     <= (others => '0');
			delayCycle   <= '0';
			oneBitRead   <= '0';
			dummy_passed <= false;
			default_state;
		end procedure reset_state;
	begin
		if rst = '1' then
			reset_state;
		elsif rising_edge(clk) then
			default_state;
			if clr = '1' then
				reset_state;
			else
				case state is
					when IDLE =>
						cs_inactive_cnt <= cs_inactive_cnt;
						prevent_retrig  <= false;
						delayCycle      <= '0';
						spi_sck         <= '0';
						oneBitRead      <= '0';
						dummy_passed    <= false;
						data_out        <= (others => '0');
						if ckDiv = clk_divider - 2 then -- ensures cs inactive time between transactions
							if cs_inactive_cnt = cs_inactive_cycles then
								if data_in_valid = '1' then
									ckDiv            <= 0;
									state            <= TX;
									bitCounter       <= 0;
									bitCounterIn     <= 0;
									data_in_length_i <= 0;
									pseudoEdge       <= true;
								end if;
							else
								ckDiv           <= 0;
								cs_inactive_cnt <= cs_inactive_cnt + 1;
							end if;
						else
							ckDiv <= ckDiv + 1;
						end if;
					when TX =>
						if data_in_valid = '0' then
							prevent_retrig <= true;
						end if;
						if ckDiv = clk_divider - 2 or pseudoEdge then
							ckDiv <= 0;
							if not pseudoEdge then
								spi_sck <= not spi_sck;
							end if;
							if spi_sck = '0' and bitCounter = data_in_length_i and data_in_valid = '0' then
								-- Prevent last clock cycle (make it a "passive" one)
								spi_sck    <= '0';
								pseudoEdge <= true;
							end if;
							if spi_sck = '1' or pseudoEdge then -- falling edge -> provide data
								if bitCounter = data_in_length_i then
									bitCounter <= 0;
									if data_in_valid = '1' and not prevent_retrig then
										shiftreg          <= data_in;
										data_in_length_i  <= data_in_length - 1;
										data_out_length_i <= data_out_length - 1;
										data_next         <= '1';
									else
										delayCycle <= '1';
									end if;
								else
									bitCounter <= bitCounter + 1;
									shiftreg   <= shiftreg(shiftreg'high - 1 downto 0) & '0';
								end if;
								--else        -- spi_sck = '1' (falling edge)
								data_out <= data_out(data_out'high - 1 downto 0) & spi_so;

								if bitCounterIn = 0 then
									if dummy_passed then
										data_out_valid <= '1';
									end if;
								end if;
								if not dummy_passed then
									if bitCounterIn = data_out_dummy_bits then
										dummy_passed <= true;
										bitCounterIn <= 1;
									else
										bitCounterIn <= bitCounterIn + 1;
									end if;
								else
									if bitCounterIn = data_out_length_i then
										bitCounterIn <= 0;
									else
										bitCounterIn <= bitCounterIn + 1;
									end if;
									if delayCycle = '1' then
										spi_sck <= '0';
										state   <= IDLE;
										ckDiv   <= 0;
									end if;
								end if;
							end if;
						else
							ckDiv <= ckDiv + 1;
						end if;
				end case;
			end if;
		end if;
	end process toSpi;
	spi_si   <= shiftreg(shiftreg'high);
	spi_cs_n <= '0' when state = TX else '1';

	transmission_active <= '1' when state = TX else '0';
end architecture RTL;
