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

library ip;
use ip.all;

entity flashrom_spi is
	port(
		clk            : in  std_logic;
		spi_clk        : in  std_logic;
		rst            : 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_reset_n    : out std_logic; -- SPI hard reset
		spi_cs_n       : out std_logic; -- SPI chip select
		spi_wp_n       : out std_logic; -- SPI write protect

		-- FPGA -> ROM
		toSpiDataIn    : in  STD_LOGIC_VECTOR(7 DOWNTO 0);
		toSpiWrite     : in  STD_LOGIC;
		toSpiFull      : out STD_LOGIC;

		-- ROM -> FPGA
		fromSpiDataOut : out STD_LOGIC_VECTOR(7 DOWNTO 0);
		fromSpiRead    : in  STD_LOGIC;
		fromSpiEmpty   : out STD_LOGIC
	);
end entity flashrom_spi;

architecture RTL of flashrom_spi is
	signal spi_rst : std_logic;

	signal toSpiRead     : STD_LOGIC;
	signal toSpiDataOut  : STD_LOGIC_VECTOR(7 DOWNTO 0);
	signal toSpiEmpty    : STD_LOGIC;
	signal fromSpiDataIn : STD_LOGIC_VECTOR(7 DOWNTO 0);
	signal fromSpiWrite  : STD_LOGIC;
	signal fromSpiFull   : STD_LOGIC;

	signal outshifter_cnt  : unsigned(2 downto 0);
	signal outshifter_data : std_logic_vector(7 downto 0);
	signal inshifter_cnt   : unsigned(2 downto 0);
	signal inshifter_data  : std_logic_vector(7 downto 0);
	signal spi_cs_n_del    : std_logic;
begin
	spi_sck <= spi_clk;
	
	resetSync : process(rst, spi_clk) is
		variable cnt : integer range 0 to 1;
	begin
		if rst = '1' then
			cnt     := 1;
			spi_rst <= '1';
		elsif rising_edge(spi_clk) then
			spi_rst <= '1';
			if cnt = 0 then
				spi_rst <= '0';
			else
				cnt := cnt - 1;
			end if;
		end if;
	end process resetSync;

	-- FPGA -> ROM
	shifter : process(spi_clk, rst) is
		variable loaded : std_logic;
	begin
		if rst = '1' then
			outshifter_data <= (others => '0');
			outshifter_cnt  <= (others => '1');
			spi_cs_n        <= '1';
			toSpiRead       <= '0';
			loaded          := '0';
		elsif rising_edge(spi_clk) then -- provide data on falling edge
			toSpiRead <= '0';
			if outshifter_cnt = "101" then
				if toSpiEmpty = '0' then
					toSpiRead <= '1';
					loaded    := '1';
				end if;
			end if;
			if outshifter_cnt = "111" then
				if loaded = '1' then
					loaded          := '0';
					outshifter_data <= toSpiDataOut;
					spi_cs_n        <= '0';
					outshifter_cnt  <= "000";
				else
					spi_cs_n <= '1';
					--shifter_cnt  <= "000";
					if toSpiEmpty = '0' then
						outshifter_cnt <= "101";
					end if;
				end if;
			else
				outshifter_cnt  <= outshifter_cnt + 1;
				outshifter_data <= outshifter_data(6 downto 0) & '0';
			end if;
		end if;
	end process shifter;
	spi_si <= outshifter_data(7);

	spiFifo_wb2spi_inst : entity ip.spiFifo
		port map(
			aclr    => rst,
			data    => toSpiDataIn,
			rdclk   => spi_clk,
			rdreq   => toSpiRead,
			wrclk   => clk,
			wrreq   => toSpiWrite,
			q       => toSpiDataOut,
			rdempty => toSpiEmpty,
			wrfull  => toSpiFull
		);

	errorHandler_wb : process(clk, rst) is
	begin
		if rst = '1' then
		elsif rising_edge(clk) then
			if toSpiWrite = '1' and toSpiFull = '1' then
				-- TODO: ERROR: Fifo full!
				report "ERROR: SPI transmit FIFO full!" severity failure;
			end if;
		end if;
	end process errorHandler_wb;

	-- ROM -> FPGA
	shifter_in : process(spi_clk, rst) is
	begin
		if rst = '1' then
			inshifter_data <= (others => '0');
			inshifter_cnt  <= (others => '0');
			fromSpiWrite   <= '0';
			spi_cs_n_del   <= '1';
		elsif rising_edge(spi_clk) then -- sample on rising edge
			spi_cs_n_del <= spi_cs_n;
			fromSpiWrite <= '0';
			if spi_cs_n = '0' then
				inshifter_cnt  <= inshifter_cnt + 1;
				inshifter_data <= inshifter_data(6 downto 0) & spi_so;
				if inshifter_cnt = "111" then
					fromSpiWrite <= '1';
				end if;
			else
				inshifter_data <= (others => '0');
				inshifter_cnt  <= (others => '0');
			end if;
		end if;
	end process shifter_in;

	spiFifo_spi2wb_inst : entity ip.spiFifo
		port map(
			aclr    => rst,
			data    => fromSpiDataIn,
			rdclk   => clk,
			rdreq   => fromSpiRead,
			wrclk   => spi_clk,
			wrreq   => fromSpiWrite,
			q       => fromSpiDataOut,
			rdempty => fromSpiEmpty,
			wrfull  => fromSpiFull
		);
	fromSpiDataIn <= inshifter_data;
end architecture RTL;
