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

library ip;
use ip.wishbone_package.all;
use ip.all;

use work.flashrom_pkg.all;

entity flashrom_wb is
	port(
		clk         : in  std_logic;
		rst         : in  std_logic;
		clr         : in  std_logic;

		-- Wishbone
		wb_in       : in  wishbone_v3_slave_in;
		wb_out      : out wishbone_v3_slave_out;

		-- 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_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
	);
end entity flashrom_wb;

architecture rtl of flashrom_wb is
	-- Controller Signals
	signal current_page      : std_logic_vector(FLASHROM_PAGE_ADDR_WIDTH - 1 downto 0);
	signal ready             : std_logic;
	signal sync_stb          : std_logic;
	signal load_stb          : std_logic;
	signal status_update_stb : std_logic;
	signal status            : std_logic_vector(7 downto 0);
	signal info              : std_logic_vector(31 downto 0);
	signal data_in           : std_logic_vector(7 downto 0);
	signal data_in_next      : std_logic;
	signal data_out          : std_logic_vector(7 downto 0);
	signal data_out_valid    : std_logic;
	--
	signal bootup_complete   : std_logic;
	type state_t is (WAITCON, IDLE, LOADPAGE, WRITEPAGE);
	signal state          : state_t;
	--
	signal requested_page : std_logic_vector(current_page'range);
	signal requested_byte : std_logic_vector(7 downto 0);
	signal dirty          : std_logic;
	-- Cache Memory
	type cache_t is array (0 to 63) of std_logic_vector(31 downto 0);
	signal cache              : cache_t;
	signal cache_addr         : std_logic_vector(7 downto 0);
	signal cache_control_addr : unsigned(7 downto 0);
	signal cache_we           : std_logic;
	signal cache_dOut         : std_logic_vector(31 downto 0);
	signal cache_dIn          : std_logic_vector(31 downto 0);
	signal data_in_shift      : std_logic_vector(31 downto 0);
	signal delay_cycle        : std_logic;
begin
	flashrom_controller_inst : entity work.flashrom_controller
		port map(
			clk               => clk,
			rst               => rst,
			clr               => clr,
			page              => current_page,
			ready             => ready,
			sync_stb          => sync_stb,
			load_stb          => load_stb,
			status_update_stb => status_update_stb,
			status            => status,
			info              => info,
			data_in           => data_in,
			data_in_next      => data_in_next,
			data_out          => data_out,
			data_out_valid    => data_out_valid,
			spi_si            => spi_si,
			spi_so            => spi_so,
			spi_sck           => spi_sck,
			spi_reset_n       => spi_reset_n,
			spi_cs_n          => spi_cs_n,
			spi_wp_n          => spi_wp_n
		);

	wb_ctrl : process(clk, rst) is
		procedure default_state is
		begin
			load_stb    <= '0';
			sync_stb    <= '0';
			wb_out.ACK  <= '0';
			cache_we    <= '0';
			delay_cycle <= '0';
		end procedure default_state;

		procedure reset_state is
		begin
			default_state;
			state           <= WAITCON;
			bootup_complete <= '0';
			dirty           <= '0';
			cache_control_addr <= (others => '0');
		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 WAITCON =>
						if ready = '1' then
							state <= IDLE;
						end if;
					when IDLE =>
						if ready = '1' and delay_cycle = '0' and bootup_complete = '1' then
							if wb_in.CYC = '1' and wb_in.STB = '1' then
								if (requested_page /= current_page) then -- Page swap required
									if (dirty = '0') then
										state              <= LOADPAGE;
										load_stb           <= '1';
										cache_control_addr <= (others => '1'); -- Wraparound...
									end if;

									if (dirty = '1') then
										state              <= WRITEPAGE;
										sync_stb           <= '1';
										cache_control_addr <= to_unsigned(1, cache_control_addr'length);
									end if;
								else    -- Same page 
									wb_out.ACK <= '1';
									if wb_in.WE = '1' then
										dirty              <= '1';
										cache_dIn          <= wb_in.DAT;
										cache_we           <= '1';
										cache_control_addr <= unsigned(wb_in.ADR(7 downto 0));
									end if;
								end if;
							end if;
						end if;

						if (bootup_complete = '0') then
							state              <= LOADPAGE;
							load_stb           <= '1';
							cache_control_addr <= (others => '1'); -- Wraparound...
						end if;
					when LOADPAGE =>
						bootup_complete <= '1';
						current_page    <= requested_page;
						if data_out_valid = '1' then
							cache_dIn          <= cache_dIn(23 downto 0) & data_out;
							cache_control_addr <= cache_control_addr + 1;
							cache_we           <= '1';
							if cache_control_addr = 254 then
								state       <= IDLE;
								delay_cycle <= '1';
							end if;
						end if;
					when WRITEPAGE =>
						if cache_control_addr = 1 then
							data_in_shift <= cache_dOut;
						end if;
						if data_in_next = '1' then
							if cache_control_addr(1 downto 0) = "00" then
								data_in_shift <= cache_dOut;
							else
								data_in_shift <= data_in_shift(23 downto 0) & x"00";
							end if;
							cache_control_addr <= cache_control_addr + 1;

							if cache_control_addr = 0 then
								state       <= IDLE;
								dirty       <= '0';
								delay_cycle <= '1';
							end if;
						end if;
				end case;
			end if;
		end if;
	end process wb_ctrl;
	requested_page <= wb_in.ADR(FLASHROM_PAGE_ADDR_WIDTH + 8 - 1 downto 8);
	requested_byte <= wb_in.ADR(7 downto 0);
	data_in        <= data_in_shift(31 downto 24);

	-- Cache Memory Controller
	cache_mem_p : process(clk, rst) is
	begin
		if rst = '1' then
			cache_dOut <= (others => '0');
		elsif rising_edge(clk) then
			cache_dOut <= cache(to_integer(unsigned(cache_addr(cache_addr'length - 1 downto 2))));
			if cache_we = '1' then
				cache(to_integer(unsigned(cache_addr(cache_addr'length - 1 downto 2)))) <= cache_dIn;
			end if;
		end if;
	end process cache_mem_p;

	cache_addr <= wb_in.ADR(7 downto 0) when (state = IDLE and cache_we = '0') else std_logic_vector(cache_control_addr);
	wb_out.DAT <= cache_dOut;
end architecture rtl;
