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
	);
	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; -- Create clock using PLL, then supply to chip and this module
		spi_cs_n            : out std_logic; -- SPI chip select

		-- Logic interface
		data_in_valid       : in  std_logic;
		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_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;
	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 dummy_passed      : boolean;
begin
	toSpi : process(clk, rst) is
		procedure default_state is
		begin
			data_next      <= '0';
			data_out_valid <= '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 =>
						delayCycle   <= '0';
						spi_sck      <= '0';
						oneBitRead   <= '0';
						dummy_passed <= false;
						data_out     <= (others => '0');
						if data_in_valid = '1' then
							state            <= TX;
							bitCounter       <= 0;
							bitCounterIn     <= 0;
							data_in_length_i <= 0;
						end if;
					when TX =>
						if ckDiv = clk_divider - 2 then
							spi_sck <= not spi_sck;
							if spi_sck = '0' then -- rising edge
								if bitCounter = data_in_length_i then
									bitCounter <= 0;
									if data_in_valid = '1' 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;
								oneBitRead <= '1';
								if bitCounterIn = 0 then
									if dummy_passed then
										data_out_valid <= '1';
									end if;
								end if;
								if not dummy_passed and bitCounterIn = data_out_dummy_bits then
									dummy_passed <= true;
									bitCounterIn <= 1;
								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;
									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;
