LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

LIBRARY WORK;
USE WORK.spi_types.all;

ENTITY spi_flash_wb IS
	Generic
	(
		FLASH_ADDR_BITS			: natural := 24;
		CACHE_ADDR_BITS			: natural := 8
	);
	Port
	(
		CLK_I		: in  STD_LOGIC;
		RST_I		: in  STD_LOGIC;
		INT_O		: out STD_LOGIC;
		CYC_I		: in  STD_LOGIC;
		STB_I		: in  STD_LOGIC;
		SEL_I		: in  unsigned(3 downto 0);
		WE_I		: in  STD_LOGIC;
		ACK_O		: out STD_LOGIC;
		SRDY_O		: out STD_LOGIC;
		MRDY_I		: in  STD_LOGIC;
		ADDR_I		: in  unsigned(31 downto 0);
		DAT_I		: in  unsigned(31 downto 0);
		DAT_O		: out unsigned(31 downto 0);
		spi_hold	: in std_logic;
		spi_ss		: out std_logic;
		spi_clk		: out std_logic;
		spi_miso	: in std_logic;
		spi_mosi	: out std_logic
	);
END spi_flash_wb;

ARCHITECTURE rtl OF spi_flash_wb IS

	type state_t is (reset, idle, spi_cmd, spi_data, cache_fill_start, cache_fill, finish);

	-- Signals for UART connections
	signal mst_cmd					: cmd_t;
	signal mst_cmd_vld				: std_logic;
	signal mst_cmd_rdy				: std_logic;
	signal mst_shift_en				: std_logic;
	signal mst_din_vld				: std_logic;
	signal mst_din_rdy				: std_logic;

	signal mst_ctrl					: ctrl_t;
	signal mst_status				: status_t;
	signal mst_din					: unsigned(31 downto 0);
	signal mst_dout					: unsigned(31 downto 0);
	signal mst_dout_re				: std_logic;
	signal mst_dout_vld				: std_logic;
	signal fill_addr				: unsigned(CACHE_ADDR_BITS-1 downto 2);
	signal data_ram_addr_rd			: unsigned(CACHE_ADDR_BITS-1 downto 2);
	signal word_addr_reg			: unsigned(CACHE_ADDR_BITS-1 downto 2);

	signal tag_we					: std_logic;
	signal tag_reg					: unsigned(FLASH_ADDR_BITS-1 downto CACHE_ADDR_BITS);
	signal tag_valid				: std_logic;
	signal tag_match				: std_logic;

	signal state					: state_t;
	signal state_next				: state_t;
	
	signal SRDY_mem					: std_logic;
	signal ACK_mem					: std_logic;
	signal DAT_mem					: unsigned(31 downto 0);
	signal data_vld					: std_logic;
	
begin
	mst_ctrl.clk_div			<= to_unsigned(0, 8);
	mst_ctrl.cpol				<= '0';
	mst_ctrl.cpha				<= '0';
	mst_ctrl.msb_first			<= '1';
	mst_dout_re					<= mst_dout_vld;
	mst_shift_en 				<= not spi_hold;
	mst_cmd						<= to_cmd(32 + 8 * 2**CACHE_ADDR_BITS, 32);
	mst_din						<= X"03" & ADDR_I(FLASH_ADDR_BITS-1 downto CACHE_ADDR_BITS) & (CACHE_ADDR_BITS-1 downto 0 => '0');
	INT_O						<= '0';

	ACK_O						<= ACK_mem;
	DAT_O						<= DAT_mem;
	SRDY_O						<= SRDY_mem;
	
	tag_match					<= tag_valid when ADDR_I(tag_reg'range) = tag_reg else '0';
	
	data_ram_addr_rd			<= word_addr_reg when tag_we = '1' else ADDR_I(CACHE_ADDR_BITS-1 downto 2);
	
inst_spi_master : entity work.spi_master
	GENERIC MAP
	(
		WORD_WIDTH		=> 32,
		CAT_FIFO_DEPTH	=> 4,
		DATA_FIFO_DEPTH	=> 4
	)
	PORT MAP
	(
		rst			=> RST_I,
		clk			=> CLK_I,
		cmd_vld		=> mst_cmd_vld,
		cmd_rdy		=> mst_cmd_rdy,
		cmd			=> mst_cmd,
		din_vld		=> mst_din_vld,
		din_rdy		=> mst_din_rdy,
		din			=> mst_din,
		dout_re		=> mst_dout_re,
		dout_vld	=> mst_dout_vld,
		dout		=> mst_dout,
		shift_en	=> mst_shift_en,
		mosi		=> spi_mosi,
		miso		=> spi_miso,
		sclk		=> spi_clk,
		mso			=> spi_ss,
		ctrl		=> mst_ctrl,
		status		=> mst_status
		
	);
	
inst_data_ram : entity work.dpram_1w1r2c_ra
	GENERIC MAP
	(
		addr_width	=> CACHE_ADDR_BITS-2,
		data_width	=> 32
	)
	PORT MAP
	(
		clk_a	=> CLK_I,
		clk_b	=> CLK_I,
		re_b	=> '1',
		we_a	=> mst_dout_vld,
		addr_a	=> fill_addr,
		addr_b	=> data_ram_addr_rd,
		din_a	=> mst_dout,
		dout_b	=> DAT_mem
	);
	

word_addr_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if (CYC_I and STB_I and SRDY_mem and not WE_I) = '1' then
				word_addr_reg	<= ADDR_I(CACHE_ADDR_BITS-1 downto 2);
			end if;
		end if;
	end process;

read_cache_ack:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			ACK_mem	<= data_vld;
		end if;
	end process;
		
tag_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				tag_valid	<= '0';
			elsif tag_we = '1' then
				tag_valid	<= '1';
				tag_reg		<= ADDR_I(tag_reg'range);
			end if;
		end if;
	end process;

fill_addr_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if mst_cmd_vld = '1' then
				fill_addr	<= (others => '0');
			elsif mst_dout_vld = '1' then
				fill_addr	<= fill_addr + 1;
			end if;
		end if;
	end process;

proc_state_next:
process(CLK_I)
begin
	if rising_edge(CLK_I) then
		if RST_I = '1' then
			state	<= reset;
		else
			state	<= state_next;
		end if;
	end if;
end process;

proc_fsm:
process(state, CYC_I, STB_I, WE_I, tag_match, mst_cmd_rdy, mst_din_rdy, mst_status)
begin
	state_next		<= state;
	SRDY_mem		<= '0';
	mst_cmd_vld		<= '0';
	mst_din_vld		<= '0';
	tag_we			<= '0';
	data_vld		<= '0';
	
	case state is

	when reset =>
		state_next			<= idle;
	
	when idle =>
		SRDY_mem			<= CYC_I;
		if (CYC_I and STB_I and not WE_I) = '1' then
			if tag_match = '0' then
				state_next	<= spi_data;
			else
				data_vld	<= '1';
			end if;
		end if;

	when spi_data =>
		mst_din_vld			<= '1';
		if mst_din_rdy = '1' then
			state_next		<= spi_cmd;
		end if;

	when spi_cmd =>
		mst_cmd_vld			<= '1';
		if mst_cmd_rdy = '1' then
			state_next		<= cache_fill_start;
		end if;

	when cache_fill_start =>
		if mst_status.xfer_busy = '1' then
			state_next		<= cache_fill;
		end if;
	
	when cache_fill =>
		if mst_status.xfer_busy = '0' then
			state_next		<= finish;
		end if;

	when finish =>
		tag_we			<= '1';
		data_vld		<= '1';
		state_next		<= idle;

	when others =>
		state_next	<= idle;

	end case;	
end process;

end rtl;
