LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
use std.textio.all; --  Imports the standard textio package.

use work.utils_pkg.all;
use work.spi_types.all;

ENTITY spi_master IS
	Generic
	(
		WORD_WIDTH			: natural := 32;
		CAT_FIFO_DEPTH		: natural := 4;
		DATA_FIFO_DEPTH		: natural := 16
	);
	Port
	(
		rst			: in  STD_LOGIC;
		clk			: in  STD_LOGIC;
		cmd_vld		: in  STD_LOGIC;
		cmd_rdy		: out STD_LOGIC;
		cmd			: in  cmd_t;
		din_vld		: in  STD_LOGIC;
		din_rdy		: out STD_LOGIC;
		din			: in  unsigned(WORD_WIDTH-1 downto 0);
		dout		: out unsigned(WORD_WIDTH-1 downto 0);
		dout_vld	: out STD_LOGIC;
		dout_re		: in  STD_LOGIC;
		shift_en	: in  STD_LOGIC;
		ctrl		: in  ctrl_t;
		status		: out status_t;
		mosi		: out STD_LOGIC;
		miso		: in  STD_LOGIC;
		sclk		: out STD_LOGIC;
		mso			: out STD_LOGIC
		
	);
END spi_master;

ARCHITECTURE behavior OF spi_master IS

	type state_t is (reset, idle, load_data, shift_write, shift_read, commit, finish);

	signal xfer_count				: unsigned(XFER_MAX_SIZE_BITS-1 downto 0);
	signal data_count				: unsigned(XFER_MAX_SIZE_BITS-1 downto 0);
	signal cmd_reg					: cmd_t;	
	signal state					: state_t;
	signal state_next				: state_t;
	signal xfer_count_en			: std_logic;
	signal xfer_count_busy			: std_logic;
	signal data_count_busy			: std_logic;
	signal spi_clk					: std_logic;
	signal spi_clk_out				: std_logic;
	signal clk_en					: std_logic;
	signal spi_clk_write			: std_logic;
	signal spi_clk_read				: std_logic;
	signal spi_rst_count			: unsigned(3 downto 0);
	signal spi_rst					: std_logic;

	signal cat_fifo_din				: unsigned(SIZEOF_CMD-1 downto 0);
	signal cat_fifo_dout			: unsigned(SIZEOF_CMD-1 downto 0);
	signal cat_fifo_full			: std_logic;
	signal cat_fifo_empty			: std_logic;
	signal cat_fifo_re				: std_logic;
	signal cat_fifo_we				: std_logic;

	signal write_fifo_din			: unsigned(WORD_WIDTH-1 downto 0);
	signal write_fifo_dout			: unsigned(WORD_WIDTH-1 downto 0);
	signal write_fifo_full			: std_logic;
	signal write_fifo_empty			: std_logic;
	signal write_fifo_we			: std_logic;

	signal read_fifo_full			: std_logic;
	signal read_fifo_empty			: std_logic;
	signal read_fifo_we				: std_logic;

	signal tx_shift_reg				: unsigned(WORD_WIDTH-1 downto 0);
	signal rx_shift_reg				: unsigned(WORD_WIDTH-1 downto 0);
	signal rx_shift_reg2			: unsigned(WORD_WIDTH-1 downto 0);
	
	signal word_cnt_pipe			: unsigned(WORD_WIDTH-1 downto 0);
	signal word_cnt_rst				: std_logic;
	signal data_load_en				: std_logic;
	signal xfer_start_en			: std_logic;
	signal ctrl_reg					: ctrl_t;
	signal slv_enable				: std_logic;
	
	signal spi_clk_div_en			: std_logic;
	signal spi_clk_div_count		: unsigned(7 downto 0);

--------------------------------------------------------------------------
begin

	sclk					<= spi_clk_out;
	cat_fifo_din			<= to_unsigned(cmd);
	write_fifo_din			<= din;
	cmd_reg 				<= to_cmd(cat_fifo_dout);
	mosi					<= tx_shift_reg(tx_shift_reg'left) when ctrl_reg.msb_first = '1' else tx_shift_reg(tx_shift_reg'right);
	spi_clk_write			<= spi_clk xor ctrl_reg.cpha;
	spi_clk_read			<= spi_clk_out;
	
	dout_vld				<= not read_fifo_empty;
	status.cmd_fifo_full	<= cat_fifo_full;
	status.cmd_fifo_empty	<= cat_fifo_empty;
	status.write_fifo_full	<= write_fifo_full;
	status.write_fifo_empty	<= write_fifo_empty;
	status.read_fifo_full	<= read_fifo_full;
	status.read_fifo_empty	<= read_fifo_empty;
	status.xfer_busy		<= '0' when state = idle else '1';
	mso						<= slv_enable;
	
--------------------------------------------------------------------------
	-- Instantiate synchronous FIFO
	inst_cat_fifo: entity work.fifo_async
	GENERIC MAP
	(
		addr_width	=> NextExpBaseTwo(CAT_FIFO_DEPTH),
		data_width	=> SIZEOF_CMD
	)
	PORT MAP
	(
		rst			=>	spi_rst,
		clk_w		=>	clk,
		clk_r		=>	spi_clk,
		we			=>	cat_fifo_we,
		re			=>	cat_fifo_re,
		fifo_full	=>	cat_fifo_full,
		fifo_empty	=>	cat_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  cat_fifo_din,
		data_r		=>  cat_fifo_dout
	);

	-- Instantiate synchronous FIFO
	inst_write_fifo: entity work.fifo_async
	GENERIC MAP
	(
		addr_width	=> NextExpBaseTwo(DATA_FIFO_DEPTH),
		data_width	=> WORD_WIDTH
	)
	PORT MAP
	(
		rst			=>	spi_rst,
		clk_w		=>	clk,
		clk_r		=>	spi_clk,
		we			=>	write_fifo_we,
		re			=>	data_load_en,
		fifo_full	=>	write_fifo_full,
		fifo_empty	=>	write_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  write_fifo_din,
		data_r		=>  write_fifo_dout
	);

	-- Instantiate synchronous FIFO
	inst_read_fifo: entity work.fifo_async
	GENERIC MAP
	(
		addr_width	=> NextExpBaseTwo(DATA_FIFO_DEPTH),
		data_width	=> WORD_WIDTH
	)
	PORT MAP
	(
		rst			=>	spi_rst,
		clk_w		=>	spi_clk,
		clk_r		=>	clk,
		we			=>	read_fifo_we,
		re			=>	dout_re,
		fifo_full	=>	read_fifo_full,
		fifo_empty	=>	read_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  rx_shift_reg2,
		data_r		=>  dout
	);

--------------------------------------------------------------------------
proc_spi_clk_divider:
process(clk)
begin
	if rising_edge(clk) then
		spi_clk_div_en <= '0';
		if rst = '1' then
			spi_clk_div_count <= (others => '0');
			spi_rst_count	<= (others => '1');
			spi_rst <= '1';
		elsif spi_clk_div_count = ctrl.clk_div then
			spi_clk_div_count	<= (others => '0');
			spi_clk_div_en		<= '1';
			if spi_rst_count /= to_unsigned(0, spi_rst_count'length) then
				spi_rst_count	<= spi_rst_count - 1;
			else
				spi_rst <= '0';
			end if;
		else
			spi_clk_div_count <= spi_clk_div_count + 1;
		end if;
	end if;
end process;

proc_spi_clk_gen:
process(clk)
begin
	if rising_edge(clk) then
		if rst = '1' then
			spi_clk	<= '0';
		elsif spi_clk_div_en = '1' then
			spi_clk	<= not spi_clk;
		end if ;
	end if;
end process;

proc_spi_clk_out_gen:
process(clk)
begin
	if rising_edge(clk) then
		if spi_rst = '1' then
			spi_clk_out	<= '0';
		elsif spi_clk_div_en = '1' then	
			if clk_en = '0' then
				spi_clk_out	<= ctrl_reg.cpol;
			else
				spi_clk_out	<= not spi_clk_out;
			end if;
		end if;
	end if;
end process;

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

proc_ctrl_reg:
process(spi_clk)
begin
	if rising_edge(spi_clk) then
		if xfer_start_en = '1' or spi_rst = '1' then
			ctrl_reg	<= ctrl;
		end if;
	end if;
end process;

proc_fsm:
process(state, cat_fifo_empty, cat_fifo_full, write_fifo_empty, write_fifo_full, read_fifo_full, xfer_count_busy, data_count_busy, word_cnt_pipe, ctrl_reg, cmd_vld, din_vld)
begin
	state_next		<= state;
	
	xfer_start_en		<= '0';
	xfer_count_en		<= '0';
	word_cnt_rst		<= '0';
	data_load_en		<= '0';
	clk_en				<= '0';
	cmd_rdy				<= not cat_fifo_full;
	din_rdy				<= not write_fifo_full;
	slv_enable			<= '1';
	read_fifo_we		<= '0';
	cat_fifo_re			<= '0';
	cat_fifo_we			<= cmd_vld;
	write_fifo_we		<= din_vld;
	case state is

	when reset =>
		cmd_rdy				<= '0';
		din_rdy				<= '0';
		slv_enable			<= '0';
		cat_fifo_we			<= '0';
		write_fifo_we		<= '0';
		state_next			<= idle;
	
	when idle =>
		slv_enable			<= '0';
		if cat_fifo_empty = '0' then
			state_next	<= load_data;
			xfer_start_en	<= '1';
			word_cnt_rst	<= '1';
		end if;

	when load_data =>
		if xfer_count_busy = '0' then
			state_next		<= finish;
		elsif write_fifo_empty = '0' then
			data_load_en	 <= '1';
			state_next		<= shift_write;
			clk_en			<= ctrl_reg.cpha;
		end if;
		
	when shift_write =>
		clk_en				<= '1';
		xfer_count_en		<= '1';
		if xfer_count_busy = '0' then
			state_next	<= finish;
		elsif data_count_busy = '1' then
			if word_cnt_pipe(word_cnt_pipe'left) = '1' then
				if write_fifo_empty = '0' then
					data_load_en 	<= '1';
				else
					state_next	<= load_data;
				end if;
			end if;
		else
			state_next	<= shift_read;
			word_cnt_rst	<= '1';
		end if;

	when shift_read =>
		clk_en				<= '1';
		xfer_count_en		<= '1';
		if xfer_count_busy = '0' or word_cnt_pipe(word_cnt_pipe'left) = '1' then
			state_next		<= commit;
		end if;
	
	when commit =>
		clk_en				<= not read_fifo_full and xfer_count_busy;
		xfer_count_en		<= not read_fifo_full and xfer_count_busy;
		read_fifo_we		<= '1';
		if read_fifo_full = '0' then
			if xfer_count_busy = '0' then
				state_next		<= finish;
			else
				state_next		<= shift_read;
			end if;
		end if;
		
	when finish =>
		cat_fifo_re		<= '1';
		state_next		<= idle;

	when others =>
		state_next	<= idle;

	end case;	
end process;

PROC_XFER_COUNT:
process(spi_clk)
begin
	if rising_edge(spi_clk) then
		if xfer_start_en = '1' then
			xfer_count			<= cmd_reg.xfer_size;
			xfer_count_busy		<= '1';
		elsif shift_en = '1' and xfer_count_en = '1' then
			if xfer_count /= to_unsigned(2, xfer_count'length) then
				xfer_count			<= xfer_count - 1;
			else
				xfer_count_busy		<= '0';
			end if;
		end if;
	end if;
end process;

PROC_WORD_COUNT:
process(spi_clk)
begin
	if rising_edge(spi_clk) then
		if word_cnt_rst = '1' then
			word_cnt_pipe		<= (word_cnt_pipe'left downto 1 => '0') & '1';
		elsif shift_en = '1' and xfer_count_en = '1' then
			if word_cnt_pipe(word_cnt_pipe'left) = '0' then
				word_cnt_pipe	<= word_cnt_pipe(word_cnt_pipe'left-1 downto 0) & '0';
			else
				word_cnt_pipe	<= (word_cnt_pipe'left downto 1 => '0') & '1';
			end if;
		end if;
	end if;
end process;

-- Transmitter
PROC_DATA_COUNT:
process(spi_clk)
begin
	if rising_edge(spi_clk) then
		if xfer_start_en = '1' then
			data_count			<= cmd_reg.data_size;
			data_count_busy		<= '1';
		elsif shift_en = '1' and xfer_count_en = '1' then
			if data_count /= to_unsigned(2, data_count'length) then
				data_count		<= data_count - 1;
			else
				data_count_busy		<= '0';
			end if;
		end if;
	end if;
end process;

PROC_TX_SHIFT_REG:
process(spi_clk_write)
begin
	if rising_edge(spi_clk_write) then
		if data_load_en = '1' then
			tx_shift_reg		<= write_fifo_dout;
		elsif shift_en = '1' and xfer_count_en = '1' then
			if (ctrl_reg.msb_first = '1') then
				tx_shift_reg		<= tx_shift_reg(tx_shift_reg'left-1 downto 0) & '0';
			else
				tx_shift_reg		<= '0' & tx_shift_reg(tx_shift_reg'left downto 1);
			end if;
		end if;
	end if;
end process;

-- Receiver
PROC_RX_SHIFT_REG:
process(spi_clk_read)
	begin
		if rising_edge(spi_clk_read) then
			if shift_en = '1' and xfer_count_en = '1' then
				if slv_enable = '1' then
					if (ctrl_reg.msb_first = '1') then
						rx_shift_reg	<= rx_shift_reg(rx_shift_reg'left-1 downto 0) & miso;
					else
						rx_shift_reg	<= miso & rx_shift_reg(rx_shift_reg'left downto 1);
					end if;
				end if;
			end if;
		end if;
	end process;

PROC_RX_SHIFT_REG2:
process(spi_clk)
	begin
		if rising_edge(spi_clk) then
			rx_shift_reg2	<= rx_shift_reg;
		end if;
	end process;

end behavior;
