----------------------------------------------------------------------------------
-- Company: 
-- Engineer: 
-- 
-- Create Date:    18:21:07 10/28/2013 
-- Design Name: 
-- Module Name:    message_parser - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
library UNISIM;
use UNISIM.VComponents.all;

library work;
use work.udp_functions_pkg.all;


entity message_parser is
   port (
		clk									: in  std_logic;
		reset_enet_counters				: out std_logic;
		message_fifo_in_wr_en			: in  std_logic;
		message_fifo_in_din				: in  std_logic_vector(8 downto 0);
		message_fifo_in_full				: out std_logic;
		message_fifo_out_rd_en			: in  std_logic;
		message_fifo_out_dout			: out std_logic_vector(63 downto 0);
		message_fifo_out_empty			: out std_logic;
		command_available					: out std_logic;
		command_contents					: out std_logic_vector(31 downto 0);
		command_reply_available			: in  std_logic;
		command_reply_contents			: in  std_logic_vector(31 downto 0);
		num_good_pkts_counter			: in  std_logic_vector(31 downto 0);
		num_bad_pkts_counter				: in  std_logic_vector(31 downto 0);
		firmware_version					: in  std_logic_vector(15 downto 0);
		send_reply							: out std_logic;
		reply_busy							: in  std_logic;
		phy_cmd_available					: out std_logic;
		phy_command							: out std_logic_vector(31 downto 0);
		phy_cmd_busy						: in  std_logic;
		phy_reply							: in  std_logic_vector(31 downto 0);
		rx_error_counter					: in  unsigned(31 downto 0);
--		rx_data_error						: in  std_logic;
--		tx_data_error						: out std_logic;
		unused_inputs						: in  std_logic_vector(1 downto 0)
	);
end message_parser;


architecture Behavioral of message_parser is
	
	component message_fifo
	port (
		clk			: in std_logic;
		din			: in std_logic_vector(8 downto 0);
		wr_en			: in std_logic;
		rd_en			: in std_logic;
		dout			: out std_logic_vector(8 downto 0);
		full			: out std_logic;
		empty			: out std_logic;
		valid			: out std_logic
	);
	end component;


	component last_reply_ram
	port (
		clka		: in std_logic;
		wea		: in std_logic_vector(0 downto 0);
		addra		: in std_logic_vector(7 downto 0);
		dina		: in std_logic_vector(63 downto 0);
		clkb		: in std_logic;
		addrb		: in std_logic_vector(7 downto 0);
		doutb		: out std_logic_vector(63 downto 0)
	);
	end component;


	component command_fifo
	port (
		clk			: in std_logic;
		din			: in std_logic_vector(63 downto 0);
		wr_en			: in std_logic;
		rd_en			: in std_logic;
		dout			: out std_logic_vector(63 downto 0);
		full			: out std_logic;
		empty			: out std_logic;
		valid			: out std_logic
	);
	end component;
		
	signal message_fifo_dout					: std_logic_vector(8 downto 0);
	signal message_fifo_empty					: std_logic;
	signal message_fifo_valid					: std_logic;
	
	signal command_fifo_valid					: std_logic;
	
	signal reply_fifo_0_valid					: std_logic;
	signal reply_fifo_1_valid					: std_logic;
	signal reply_fifo_0_empty					: std_logic;
	signal reply_fifo_1_empty					: std_logic;
--	signal reply_fifo_full						: std_logic;
	
	type   fifo_command_type is (idle, waitFifoCommandComplete);
	signal fifo_command_state					: fifo_command_type := idle;
	signal command_available_i					: std_logic := '0';
	signal command_contents_i					: std_logic_vector(31 downto 0) := (others => '0');
	signal fifo_reply_available				: std_logic := '0';
	signal cpu_command_busy						: std_logic := '0';
	
	type   cpu_command_type is (idle, checkCommand, waitCommandStarted, waitCommandComplete);
	signal cpu_command_state					: cpu_command_type := idle;
	signal cpu_command_do_fifo					: std_logic := '0';
	signal cpu_command_contents				: std_logic_vector(31 downto 0) := (others => '0');
	signal cpu_command_reply_available		: std_logic := '0';
	signal cpu_command_reply					: std_logic_vector(31 downto 0) := (others => '0');
	
	signal last_reply_dout						: std_logic_vector(63 downto 0) := (others => '0');
	
	
	type   phy_command_type is (idle, waitCommandStarted, waitCommandComplete);
	signal phy_command_state					: phy_command_type := idle;
	signal phy_cmd_available_i					: std_logic := '0';
	signal phy_command_i							: std_logic_vector(31 downto 0) := (others => '0');
	signal phy_command_reply_available		: std_logic := '0';
	signal phy_command_reply					: std_logic_vector(31 downto 0) := (others => '0');
	
	
	type   enet_stats_type is (idle, checkCommand, waitRAM_MSW, readRAM_MSW, waitRAM_LSW, readRAM_LSW);
	signal enet_stats_state						: enet_stats_type := idle;
	signal last_reply_address					: std_logic_vector(7 downto 0) := (others => '0');
	signal enet_stats_reply_available		: std_logic := '0';
	signal enet_stats_reply						: std_logic_vector(31 downto 0) := (others => '0');
	signal reset_enet_counters_i				: std_logic := '0';
--	signal tx_data_error_i						: std_logic := '0';
	
	
	type   message_parse_type is (idle, checkSubsystem, waitCPUCommandDone, waitPHYCommandDone,
																							getEnetStatsReply, parseDone);
	signal message_parse_state					: message_parse_type := idle;
	signal parse_done								: std_logic := '0';
	signal message_result						: std_logic_vector(63 downto 0) := (others => '0');
	signal cpu_command_available				: std_logic := '0';
	signal phy_command_available				: std_logic := '0';
	signal enet_stats_request					: std_logic := '0';
	
	signal bad_command_counter					: unsigned(15 downto 0) := (others => '0');
	
	type   message_receive_type is (idle, waitDataReady, readMessage, waitParseDone,
													checkForNextMessage, sendMessage);
	signal message_receive_state				: message_receive_type := idle;
	signal message_byte_counter				: unsigned(2 downto 0) := (others => '0');
	signal message_sub_system					: std_logic_vector(7 downto 0) := (others => '0');
	signal message_command_byte				: std_logic_vector(7 downto 0) := (others => '0');
	signal message_id								: std_logic_vector(15 downto 0) := (others => '0');
	signal msg_command_contents_i				: std_logic_vector(31 downto 0) := (others => '0');
	signal new_message							: std_logic := '0';
	signal bad_command							: std_logic := '0';
	signal message_fifo_rd_en					: std_logic := '0';
	signal command_fifo_din						: std_logic_vector(63 downto 0) := (others => '0');
	signal command_fifo_wr_en					: std_logic := '0';
	signal last_reply_wr_en						: std_logic_vector(0 downto 0) := (others => '0');
	signal last_message_commands				: unsigned(7 downto 0) := (others => '0');
	signal message_commands_counter			: unsigned(7 downto 0) := (others => '0');
	
begin
	reset_enet_counters <= reset_enet_counters_i;
--	tx_data_error <= tx_data_error_i;


	message_fifo_in_0 : message_fifo
	port map (
		clk			=> clk,
		din			=> message_fifo_in_din,
		wr_en			=> message_fifo_in_wr_en,
		rd_en			=> message_fifo_rd_en,
		dout			=> message_fifo_dout,
		full			=> message_fifo_in_full,
		empty			=> message_fifo_empty,
		valid			=> message_fifo_valid
	);


	message_fifo_out_0 : command_fifo
	port map (
		clk			=> clk,
		din			=> command_fifo_din,
		wr_en			=> command_fifo_wr_en,
		rd_en			=> message_fifo_out_rd_en,
		dout 			=> message_fifo_out_dout,
		full 			=> open,
		empty 		=> message_fifo_out_empty,
		valid 		=> command_fifo_valid
	);

	command_available <= command_available_i;
	command_contents <= command_contents_i;

	fifo_command_proc : process (clk)
	begin
		if rising_edge(clk) then
			fifo_reply_available <= '0';
			command_available_i <= '0';
			
			case fifo_command_state is
				when idle =>
					if (cpu_command_do_fifo = '1') then
						command_contents_i <= cpu_command_contents;
						command_available_i <= '1';
						cpu_command_busy <= '1';
						fifo_command_state <= waitFifoCommandComplete;
					else
						cpu_command_busy <= '0';
						fifo_command_state <= idle;
					end if;
				
				when waitFifoCommandComplete =>
					if (command_reply_available = '1') then
						fifo_reply_available <= '1';
						fifo_command_state <= idle;
					else
						fifo_command_state <= waitFifoCommandComplete;
					end if;
			end case;
		end if;
	end process fifo_command_proc;
			

	cpu_command_parser_proc : process (clk)
	begin
		if rising_edge(clk) then
			cpu_command_reply_available <= '0';
			cpu_command_do_fifo <= '0';
			
			case cpu_command_state is
				when idle =>
					if (cpu_command_available = '1') then
						cpu_command_state <= checkCommand;
					else
						cpu_command_state <= idle;
					end if;
				
				when checkCommand =>
					case message_command_byte is
						when x"01" =>		-- Do FIFO command
							cpu_command_contents <= msg_command_contents_i;
							cpu_command_do_fifo <= '1';
							cpu_command_state <= waitCommandStarted;
						
						when x"02" =>		-- Get firmware version
							cpu_command_reply <= x"0000" & firmware_version;
							cpu_command_reply_available <= '1';
							cpu_command_state <= idle;
						
						when others =>		-- Undefined command
							cpu_command_reply <= x"ffffffff";
							cpu_command_reply_available <= '1';
							cpu_command_state <= idle;
						
					end case;
				
				when waitCommandStarted =>
					cpu_command_do_fifo <= '1';
					if (cpu_command_busy = '0') then
						cpu_command_state <= waitCommandStarted;
					else
						cpu_command_state <= waitCommandComplete;
					end if;
				
				when waitCommandComplete =>
					if (fifo_reply_available = '1') then
						cpu_command_reply_available <= '1';
						cpu_command_reply <= command_reply_contents;
						cpu_command_state <= idle;
					else
						cpu_command_state <= waitCommandComplete;
					end if;
			end case;
		end if;
	end process cpu_command_parser_proc;
			

	phy_cmd_available <= phy_cmd_available_i;
	phy_command <= phy_command_i;

	phy_command_parser_proc : process (clk)
	begin
		if rising_edge(clk) then
			phy_command_reply_available <= '0';
			phy_cmd_available_i <= '0';
			
			case phy_command_state is
				when idle =>
					if (phy_command_available = '1') then
						phy_command_state <= waitCommandStarted;
					else
						phy_command_state <= idle;
					end if;
				
				when waitCommandStarted =>
					phy_cmd_available_i <= '1';
					phy_command_i <= msg_command_contents_i;
					if (phy_cmd_busy = '0') then
						phy_command_state <= waitCommandStarted;
					else
						phy_command_state <= waitCommandComplete;
					end if;
				
				when waitCommandComplete =>
					if (phy_cmd_busy = '1') then
						phy_command_state <= waitCommandComplete;
					else
						phy_command_reply_available <= '1';
						phy_command_reply <= phy_reply;
						phy_command_state <= idle;
					end if;
			end case;
		end if;
	end process phy_command_parser_proc;
	


	last_reply_ram_0 : last_reply_ram
	port map (
		clka		=> clk,
		wea		=> last_reply_wr_en,
		addra		=> std_logic_vector(message_commands_counter),
		dina		=> message_result,
		clkb		=> clk,
		addrb		=> last_reply_address,
		doutb		=> last_reply_dout
	);


	enet_stats_reply_proc : process (clk)
	begin
		if rising_edge(clk) then
			reset_enet_counters_i <= '0';
			enet_stats_reply_available <= '0';
			
			case enet_stats_state is
				when idle =>
					if (enet_stats_request = '1') then
						enet_stats_state <= checkCommand;
					else
						last_reply_address <= x"ff";
						enet_stats_state <= idle;
					end if;
				
				when checkCommand =>
					case message_command_byte is
						when x"01" =>
							reset_enet_counters_i <= '1';
							enet_stats_reply <= (others => '0');
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
						
						when x"02" =>
							enet_stats_reply <= num_good_pkts_counter;
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
						
						when x"03" =>
							enet_stats_reply <= num_bad_pkts_counter;
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
						
						when x"04" =>
							enet_stats_reply <= x"0000" & std_logic_vector(bad_command_counter);
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
						
						when x"05" =>
							enet_stats_reply <= x"000000" & std_logic_vector(last_message_commands);
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
						
						when x"06" =>
							last_reply_address <= msg_command_contents_i(7 downto 0);
							enet_stats_state <= waitRAM_MSW;
						
						when x"07" =>
							last_reply_address <= msg_command_contents_i(7 downto 0);
							enet_stats_state <= waitRAM_LSW;
						
						when x"08" =>
							enet_stats_reply <= std_logic_vector(rx_error_counter);
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
						
--						when x"7E" =>
--							tx_data_error_i <= msg_command_contents_i(0);
--							enet_stats_reply <= x"00000000";
--							enet_stats_reply_available <= '1';
--							enet_stats_state <= idle;
						
						when x"7F" =>
							enet_stats_reply <= x"0000" & x"00" & "000000" & unused_inputs;
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
						
						when others =>
							enet_stats_reply <= x"ffffffff";
							enet_stats_reply_available <= '1';
							enet_stats_state <= idle;
					end case;
				
				when waitRAM_MSW =>
					enet_stats_state <= readRAM_MSW;
				
				when readRAM_MSW =>
					enet_stats_reply <= last_reply_dout(63 downto 32);
					enet_stats_reply_available <= '1';
					enet_stats_state <= idle;
				
				when waitRAM_LSW =>
					enet_stats_state <= readRAM_LSW;
				
				when readRAM_LSW =>
					enet_stats_reply <= last_reply_dout(31 downto 0);
					enet_stats_reply_available <= '1';
					enet_stats_state <= idle;
					
			end case;
		end if;
	end process enet_stats_reply_proc;

	
	message_parser_proc : process (clk)
	begin
		if rising_edge(clk) then
			parse_done <= '0';
			cpu_command_available <= '0';
			phy_command_available <= '0';
			enet_stats_request <= '0';
	
			case message_parse_state is
				when idle =>
					if (new_message = '1') then
						message_parse_state <= checkSubsystem;
					else
						message_parse_state <= idle;
					end if;
				
				when checkSubsystem =>
					case message_sub_system is
						when x"01" =>
							cpu_command_available <= '1';
							message_parse_state <= waitCPUCommandDone;
						
						when x"02" =>
							phy_command_available <= '1';
							message_parse_state <= waitPHYCommandDone;
						
						when x"08" =>
							enet_stats_request <= '1';
							message_parse_state <= getEnetStatsReply;
						
						when others =>
							message_result <= "1111" & message_sub_system(3 downto 0) & message_command_byte &
															message_id & msg_command_contents_i(31 downto 8) & message_fifo_dout(7 downto 0);
							message_parse_state <= parseDone;
					end case;
				
				when waitCPUCommandDone =>
					if (cpu_command_reply_available = '1') then
						message_result <= message_sub_system & message_command_byte &
																				message_id & cpu_command_reply;
						message_parse_state <= parseDone;
					else
						message_parse_state <= waitCPUCommandDone;
					end if;
				
				when waitPHYCommandDone =>
					if (phy_command_reply_available = '1') then
						message_result <= message_sub_system & message_command_byte &
																				message_id & phy_command_reply;
						message_parse_state <= parseDone;
					else
						message_parse_state <= waitPHYCommandDone;
					end if;
				
				when getEnetStatsReply =>
					if (enet_stats_reply_available = '1') then
						message_result <= message_sub_system & message_command_byte &
																				message_id & enet_stats_reply;
						message_parse_state <= parseDone;
					else
						message_parse_state <= getEnetStatsReply;
					end if;
				
				when parseDone =>
					parse_done <= '1';
					message_parse_state <= idle;
			end case;
		end if;
	end process message_parser_proc;


	bad_command_counter_proc : process (clk)
	begin
		if rising_edge(clk) then
			if (reset_enet_counters_i = '1') then
				bad_command_counter <= (others => '0');
			elsif (bad_command = '1') then
				bad_command_counter <= bad_command_counter + 1;
			end if;
		end if;
	end process bad_command_counter_proc;

	
	message_receive_proc : process (clk)
	begin
		if rising_edge(clk) then
			message_fifo_rd_en <= '0';
			command_fifo_wr_en <= '0';
			send_reply <= '0';
			new_message <= '0';
			bad_command <= '0';
			last_reply_wr_en <= "0";
			
			case message_receive_state is
				when idle =>
					if (message_fifo_empty = '0') then
						message_byte_counter <= (others => '0');
						message_commands_counter <= (others => '0');
						message_fifo_rd_en <= '1';
						message_receive_state <= waitDataReady;
					else
						message_receive_state <= idle;
					end if;
				
				when waitDataReady =>
					message_fifo_rd_en <= '1';
					message_receive_state <= readMessage;
				
				when readMessage =>
					message_byte_counter <= message_byte_counter + 1;
					if (message_fifo_dout(8) = '1') and (message_byte_counter /= "111") then	-- bad command
						bad_command <= '1';
						command_fifo_din <= x"ffffffffffffffff";
						command_fifo_wr_en <= '1';
						message_receive_state <= checkForNextMessage;	-- send error reply
					else
						case message_byte_counter is
							when "000" =>
								message_fifo_rd_en <= '1';
								message_sub_system <= message_fifo_dout(7 downto 0);
								message_receive_state <= readMessage;
								
							when "001" =>
								message_fifo_rd_en <= '1';
								message_command_byte <= message_fifo_dout(7 downto 0);
								message_receive_state <= readMessage;
								
							when "010" =>
								message_fifo_rd_en <= '1';
								message_id(15 downto 8) <= message_fifo_dout(7 downto 0);
								message_receive_state <= readMessage;
								
							when "011" =>
								message_fifo_rd_en <= '1';
								message_id(7 downto 0) <= message_fifo_dout(7 downto 0);
								message_receive_state <= readMessage;
								
							when "100" =>
								message_fifo_rd_en <= '1';
								msg_command_contents_i(31 downto 24) <= message_fifo_dout(7 downto 0);
								message_receive_state <= readMessage;
								
							when "101" =>
								message_fifo_rd_en <= '1';
								msg_command_contents_i(23 downto 16) <= message_fifo_dout(7 downto 0);
								message_receive_state <= readMessage;
								
							when "110" =>
								msg_command_contents_i(15 downto 8) <= message_fifo_dout(7 downto 0);
								message_receive_state <= readMessage;
								
							when others =>
								msg_command_contents_i(7 downto 0) <= message_fifo_dout(7 downto 0);
								new_message <= '1';
								message_receive_state <= waitParseDone;
						end case;
					end if;
				
				when waitParseDone =>
					if (parse_done = '1') then
						if (message_sub_system /= x"08") then
							last_reply_wr_en <= "1";
							last_message_commands <= message_commands_counter + 1;
						end if;
						command_fifo_din <= message_result;
						command_fifo_wr_en <= '1';
						message_receive_state <= checkForNextMessage;
					else
						message_receive_state <= waitParseDone;
					end if;
				
				when checkForNextMessage =>
					if (message_fifo_dout(8) = '1') then
						send_reply <= '1';
						message_receive_state <= sendMessage;
					else
						message_commands_counter <= message_commands_counter + 1;
						message_fifo_rd_en <= '1';
						message_receive_state <= waitDataReady;
					end if;
				
				when sendMessage =>
					if (reply_busy = '1') then
						message_receive_state <= sendMessage;
					else
						send_reply <= '1';
						message_receive_state <= idle;
					end if;
				
--				when sendMessage_2 =>
--					if (udp_send_busy = '1') then
--						message_receive_state <= sendMessage_2;
--					else
--						message_receive_state <= idle;
--					end if;
			end case;
			
		end if;
	end process message_receive_proc;

end Behavioral;

