--------------------------------------------------------------------------------
-- Company: 
-- Engineer:
--
-- Create Date:    18:05:41 10/22/05
-- Design Name:    
-- Module Name:    ps2_core - Behavioral
-- Project Name:   
-- Target Device:  
-- Tool versions:  
-- Description:
--
-- Dependencies:
-- 
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
-- 
--------------------------------------------------------------------------------
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

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

entity ps2_core is
	Generic 
	(
		f_sys_clk	: real	:= 100.0
	);
    Port
    (
		rst : in std_logic;
		clk : in std_logic;
		din : in unsigned(7 downto 0);
		dout : out unsigned(7 downto 0);
		tx_empty : out std_logic;
		rx_present : out std_logic;
		rx_err_flag : out std_logic;
		rx_err_par : out std_logic;
		din_vld : in std_logic;
		read_en : in std_logic;
		ps2_clk_rx : in std_logic;
		ps2_data_rx : in std_logic;
		ps2_clk_tx : out std_logic;
		ps2_data_tx : out std_logic
	);

end ps2_core;

architecture Behavioral of ps2_core is

-- Types
type line_state_rx_t is (start_wait_st, start_trans_st, start_sample_st,
                      data_wait_st, data_trans_st, data_sample_st, 
                      parity_wait_st, parity_trans_st, parity_sample_st, 
                      stop_wait_st, stop_trans_st, stop_sample_st,
							 valid_st, abort_st);

type line_state_tx_t is (tx_idle_st, tx_clock_assert_st, tx_start_latency_st,
                         tx_start_assert_st, tx_clock_release_st, tx_dev_trans_st,
								 tx_data_wait_st, tx_data_trans_st, tx_data_sample_st,
								 tx_parity_update_st, tx_dev_wait_st, 
								 tx_parity_wait_st, tx_parity_trans_st, tx_parity_sample_st,
								 tx_stop_wait_st, tx_stop_trans_st, tx_stop_sample_st,
								 tx_ack_trans_st, tx_ack_sample_st, tx_valid_st);

-- Constant
constant prescaler_us          : integer := integer(f_sys_clk);
constant timeout_us            : integer := 15000;
constant time_clock_assert     : integer := 120;
constant time_clock_abort      : integer := 500;

-- Signals
signal en_us               : std_logic;
signal timeout_rx_load_en     : std_logic;
signal timeout_rx_flag     : std_logic;

signal timeout_tx_load_en  : std_logic;
signal timeout_tx_flag     : std_logic;

signal read_cs             : line_state_rx_t;
signal read_ns             : line_state_rx_t;
signal write_cs            : line_state_tx_t;
signal write_ns            : line_state_tx_t;

signal data_cnt_en         : std_logic;
signal data_cnt_rst        : std_logic;
signal data_cnt            : integer range 0 to 7;

signal data_cnt_tx_en      : std_logic;
signal data_cnt_tx_rst     : std_logic;
signal data_cnt_tx         : integer range 0 to 7;

signal sipo_en             : std_logic;
signal piso_en             : std_logic;
signal piso_out            : std_logic;
signal piso_load_en        : std_logic;
signal rx_en               : std_logic;
signal tx_en               : std_logic;
signal rx_busy             : std_logic;
signal rx_full             : std_logic;
signal rx_err              : std_logic;
signal parity_rx_err_en    : std_logic;
signal rx_err_set 	   		: std_logic;
signal rx_err_clr   	   : std_logic;

signal tx_busy             : std_logic;
signal rx_rdy              : std_logic;
signal tx_rdy              : std_logic;
signal tx_req              : std_logic;
signal rx_reg              : unsigned(7 downto 0);
signal tx_reg              : unsigned(7 downto 0);

signal parity_rx_rst       : std_logic;
signal parity_rx_en        : std_logic;
signal parity_rx           : std_logic;
signal parity_tx_rst       : std_logic;
signal parity_tx_en        : std_logic;
signal parity_tx           : std_logic;

signal timeout_count_rx		: integer range 0 to timeout_us-1;
signal timeout_count_tx		: integer range 0 to timeout_us-1;
signal timeval_tx			: integer range 0 to timeout_us-1;
signal timeval_rx			: integer range 0 to timeout_us-1;

signal sipo_reg				: unsigned(7 downto 0);
signal piso_reg				: unsigned(7 downto 0);

signal clk_tx  		        : std_logic;
signal data_tx  	        : std_logic;
signal rx_abort		        : std_logic;

begin

proc_prescaler:
process(clk)
	variable cnt_prescaler : integer range 0 to prescaler_us-1;
	begin
	if rising_edge(clk) then
		if rst = '1' then
			cnt_prescaler := prescaler_us - 1;
			en_us <= '0';
		elsif cnt_prescaler = 0 then
			cnt_prescaler := prescaler_us - 1;
			en_us <= '1';
		else
			cnt_prescaler := cnt_prescaler - 1;
			en_us <= '0';
		end if;
	end if;
end process proc_prescaler;

proc_timeout_counter_rx:
process (clk) 
begin
	if rising_edge(clk) then
		if timeout_rx_load_en ='1' then
			timeout_count_rx <= timeval_rx;
			timeout_rx_flag <= '0';
		elsif en_us = '1' then
			if (timeout_count_rx /= 0) then
				timeout_count_rx <= timeout_count_rx - 1;
			else
				timeout_rx_flag <= '1';
			end if;		
		end if;
	end if;
end process;

proc_timeout_counter_tx:
process (clk) 
begin
	if rising_edge(clk) then
		if timeout_tx_load_en ='1' then
			timeout_count_tx <= timeval_tx;
			timeout_tx_flag <= '0';
		elsif en_us = '1' then
			if (timeout_count_tx /= 0) then
				timeout_count_tx <= timeout_count_tx - 1;
			else
				timeout_tx_flag <= '1';
			end if;		
		end if;
	end if;
end process;

proc_rx_sipo:
process (clk)
begin
	if rising_edge(clk) then
		if (rst = '1') then
			sipo_reg <= (others => '0'); 
		elsif sipo_en = '1' then 
			sipo_reg <= ps2_data_rx & sipo_reg(sipo_reg'left downto 1);
		end if; 
	end if;
end process;

proc_tx_piso:
process (clk)
begin
	if rising_edge(clk) then
		if rst ='1' then 
			piso_reg <= (others => '0'); 
		elsif piso_load_en = '1' then 
			piso_reg <= tx_reg; 
		elsif piso_en = '1' then 
			piso_reg <= '0' & piso_reg(piso_reg'left downto 1);
		end if; 
	end if;
end process;

proc_parity_bitser_rx:
process (clk) 
begin
	if rising_edge(clk) then
		if parity_rx_rst='1' then 
			parity_rx <= '1';
		elsif parity_rx_en = '1' and ps2_data_rx = '1' then
			parity_rx <= not parity_rx;
		end if;
	end if;
end process;

proc_rx_error_reg:
process (clk) 
begin
	if rising_edge(clk) then
		if rst='1' then 
			rx_err_flag <= '0';
			rx_err_par <= '0';
		elsif parity_rx_err_en = '1' then
			rx_err_flag <= parity_rx;
			rx_err_par <= parity_rx;
		elsif rx_err_set = '1' then
			rx_err_flag <= '1';
		elsif rx_err_clr = '1' then
			rx_err_flag <= '0';
		end if;
	end if;
end process;

proc_parity_bitser_tx:
process (clk) 
begin
	if rising_edge(clk) then
		if parity_tx_rst='1' then 
			parity_tx <= '1';
		elsif parity_tx_en = '1' and piso_out = '1' then
			parity_tx <= not parity_tx;
		end if;
	end if;
end process;


	-------------------------------------------
	tx_en <= tx_req and (not rx_busy);
	rx_en <= not tx_busy;
	rx_reg <= sipo_reg;
	piso_out <= piso_reg(0);

	-------------------------------------------
rx_present <= rx_full;
proc_out_reg:
process(clk)
begin
	if rising_edge(clk) then
		ps2_clk_tx		<= clk_tx and not rx_abort;
		ps2_data_tx		<= data_tx;
		if rst = '1' then
			dout      <= (others => '0');
			rx_full <= '0';
		elsif rx_rdy = '1' then
			dout      <= rx_reg;
			rx_full <= '1';
		elsif read_en = '1' then
			rx_full <= '0';
		end if;
	end if;
end process;

proc_in_reg:
process(clk)
begin
	if rising_edge(clk) then
		if rst = '1' then
			tx_empty    <= '1';
			tx_req       <= '0';
		elsif piso_load_en = '1' then
			tx_reg       <= din;
			tx_empty    <= '1';
			tx_req       <= '0';
		elsif	din_vld = '1' then
			tx_empty    <= '0';
			tx_req       <= '1';
		end if;
	end if;
end process;

	-------------------------------------------
proc_read_state_clk:
process(clk)
begin
	if rising_edge(clk) then
		if rst = '1' then
			read_cs <= start_wait_st;
		else
			read_cs <= read_ns;
			if (data_cnt_rst = '1') then
				data_cnt <= 7;
			elsif (data_cnt_en = '1') then
				if (data_cnt /= 0) then
					data_cnt <= data_cnt - 1;
				end if;
			end if;
		end if;
	end if;
end process;

	-------------------------------------------
proc_write_state_clk:
process(rst, clk)
begin
	if rising_edge(clk) then
		if rst = '1' then
			write_cs <= tx_idle_st;
		else	
			write_cs <= write_ns;
			if (data_cnt_tx_rst = '1') then
				data_cnt_tx <= 7;
			elsif (data_cnt_tx_en = '1') then
				if (data_cnt_tx /= 0) then
					data_cnt_tx <= data_cnt_tx - 1;
				end if;
			end if;
		end if;
	end if;
end process;

read_state_decode:
process (read_cs, timeout_rx_flag, rx_en, data_cnt, ps2_data_rx, ps2_clk_rx, rx_full)
begin
	timeout_rx_load_en <= '0';
	timeval_rx			<= timeout_us - 1;
	data_cnt_en     <= '0';
	data_cnt_rst    <= '0';
	sipo_en         <= '0';
	rx_rdy          <= '0';
	parity_rx_en 		 <= '0';
	parity_rx_rst		 <= '0';
	parity_rx_err_en	<= '0';
	rx_busy         <= '1';
	rx_abort		<= '0';
	rx_err_set	<= '0';
	rx_err_clr	<= '0';
	
      --declare default state for next_state to avoid latches
      read_ns <= read_cs;  --default is to stay in current state
      --insert statements to decode next_state
      --below is a simple example
      case (read_cs) is
         when start_wait_st =>
			timeout_rx_load_en <= '1';
			rx_busy         <= '0';
				if ps2_data_rx = '0' and ps2_clk_rx = '1' and rx_en = '1' then
               read_ns <= start_trans_st;
            end if;
         when start_trans_st =>
			data_cnt_rst    <= '1';
			parity_rx_rst		 <= '1';
			timeout_rx_load_en <= '1';
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_data_rx = '0' and ps2_clk_rx = '0' then
               read_ns <= start_sample_st;
            end if;
         when start_sample_st =>
				read_ns <= data_wait_st;
         -- Wait data change
			when data_wait_st =>
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_rx = '1' then
               read_ns <= data_trans_st;
            end if;
         -- Wait data trans
         when data_trans_st =>
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_rx = '0' then
               read_ns <= data_sample_st;
            end if;
         -- Data sample
         when data_sample_st =>
			data_cnt_en     <= '1';	
			parity_rx_en 		 <= '1';
			sipo_en			 <= '1';	
			timeout_rx_load_en <= '1';
				read_ns <= data_wait_st;
				if data_cnt = 0 then
	            read_ns <= parity_wait_st;
				end if;
         when parity_wait_st =>
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_rx = '1' then
               read_ns <= parity_trans_st;
            end if;
         when parity_trans_st =>
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_rx = '0' then
               read_ns <= parity_sample_st;
            end if;
         when parity_sample_st =>
			timeout_rx_load_en <= '1';
			parity_rx_en 		 <= '1';
				read_ns <= stop_wait_st;
				if rx_full = '1' then -- buffer full
					timeval_rx <=  time_clock_abort - 1;
					read_ns <= abort_st;
					rx_err_set <= '1';
				end if;	
         when stop_wait_st =>
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_rx = '1' then
               read_ns <= stop_trans_st;
            end if;
         when stop_trans_st =>
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_rx = '0' and ps2_data_rx = '1' then
               read_ns <= stop_sample_st;
            end if;
         when stop_sample_st =>
				if timeout_rx_flag = '1' then
               read_ns <= start_wait_st;
				elsif ps2_clk_rx = '1' then
               read_ns <= valid_st;
            end if;
         when valid_st =>
			parity_rx_err_en	<= '1';
			rx_busy         <= '0';
			rx_rdy          <= '1';
			rx_err_clr <= '1';
            read_ns <= start_wait_st;
         when abort_st =>
         	rx_abort <= '1';
			if timeout_rx_flag = '1' then
            	read_ns <= start_wait_st;
            end if;
         when others =>
            read_ns <= start_wait_st;
      end case;      
   end process;

	-------------------------------------------
write_state_output:
process(write_cs, piso_out, parity_tx)
	begin
	
		timeout_tx_load_en <= '0';
		tx_rdy             <= '0';
		tx_busy            <= '1';
		piso_load_en		 <= '0';
      clk_tx        <= '1';
      data_tx       <= '1';
		piso_en            <= '0';
		timeval_tx         <= time_clock_assert - 1;
		data_cnt_tx_en		 <= '0';
		data_cnt_tx_rst    <= '0';
		parity_tx_rst      <= '0';
		parity_tx_en       <= '0';
      case (write_cs) is
         when tx_idle_st =>
				tx_busy            <= '0';
				timeout_tx_load_en <= '1';
         when tx_clock_assert_st =>
				parity_tx_rst      <= '1';
				piso_load_en		 <= '1';
				data_cnt_tx_rst    <= '1';
		      clk_tx        <= '0';
         when tx_start_assert_st =>
		      clk_tx        <= '0';
            data_tx       <= '0';
         when tx_clock_release_st =>
            data_tx       <= '0';
				timeout_tx_load_en <= '1';
				timeval_tx         <=  timeout_us - 1;
         when tx_dev_wait_st =>
            data_tx       <= '0';
         when tx_dev_trans_st =>
            data_tx       <= '0';
				timeout_tx_load_en <= '1';
				timeval_tx         <=  timeout_us - 1;
         when tx_data_wait_st => 
				data_tx       <= piso_out;
         when tx_data_trans_st => 
				data_cnt_tx_en		 <= '1';
				data_tx       <= piso_out;
				piso_en            <= '1';
				timeout_tx_load_en <= '1';
				timeval_tx         <=  timeout_us - 1;
				parity_tx_en       <= '1';
         when tx_data_sample_st => 
				data_tx       <= piso_out;
         when tx_parity_update_st => 
				parity_tx_en       <= '1';
				data_tx       <= piso_out;
         when tx_parity_sample_st => 
				data_tx       <= parity_tx;
         when tx_parity_wait_st => 
				data_tx       <= parity_tx;
         when tx_parity_trans_st => 
				data_tx       <= piso_out;
				timeout_tx_load_en <= '1';
				timeval_tx         <=  timeout_us - 1;
         when tx_valid_st => 
				tx_rdy             <= '1';
         when others => null; 
      end case;      
	end process;

write_state_decode:
process (write_cs, tx_en, data_cnt_tx, timeout_tx_flag, ps2_data_rx, ps2_clk_rx)
   begin
      --declare default state for next_state to avoid latches
      write_ns <= write_cs;  --default is to stay in current state
      --insert statements to decode next_state
      --below is a simple example
      case (write_cs) is
			when tx_idle_st =>
				if tx_en = '1' then
					write_ns <= tx_clock_assert_st;
				end if;
         when tx_clock_assert_st =>
				if timeout_tx_flag = '1' then
	            write_ns <= tx_start_assert_st;
            end if;
         when tx_start_assert_st =>
				write_ns <= tx_clock_release_st;
         when tx_clock_release_st =>
            write_ns <= tx_dev_wait_st;
         when tx_dev_wait_st =>
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '1' then
	            write_ns <= tx_dev_trans_st;
            end if;
         when tx_dev_trans_st =>
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '0' then
	            write_ns <= tx_data_wait_st;
            end if;
         when tx_data_wait_st =>
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '1' then
	            write_ns <= tx_data_sample_st;
            end if;
         when tx_data_sample_st => 
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '0' then
	            write_ns <= tx_data_trans_st;
					if (data_cnt_tx = 0)	then
						write_ns <= tx_parity_update_st;
					end if;		
            end if;
         when tx_data_trans_st => 
				write_ns <= tx_data_wait_st;
         when tx_parity_update_st => 
				write_ns <= tx_parity_trans_st;
         when tx_parity_trans_st => 
            write_ns <= tx_parity_wait_st;
         when tx_parity_wait_st =>
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '1' then
	            write_ns <= tx_parity_sample_st;
            end if;
         when tx_parity_sample_st => 
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '0' then
	            write_ns <= tx_stop_trans_st;
            end if;
         when tx_stop_trans_st => 
            write_ns <= tx_stop_wait_st;
         when tx_stop_wait_st =>
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '1' then
	            write_ns <= tx_ack_trans_st;
            end if;
         when tx_ack_trans_st => 
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '0' and ps2_data_rx = '0' then
	            write_ns <= tx_ack_sample_st;
            end if;
         when tx_ack_sample_st => 
				if timeout_tx_flag = '1' then
					write_ns <= tx_idle_st;
				elsif ps2_clk_rx = '1' and ps2_data_rx = '1' then
	            write_ns <= tx_valid_st;
            end if;
         when others =>
            write_ns <= tx_idle_st;
      end case;      
   end process;

	-------------------------------------------
end Behavioral;
