--------------------------------------------------------------------------------
-- 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.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.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;
           ce : in std_logic;
           d_in : in std_logic_vector(7 downto 0);
           d_out : out std_logic_vector(7 downto 0);
           input_rdy : out std_logic;
           output_rdy : out std_logic;
           write_en : in std_logic;
           read_en : in std_logic;
           ps2_clk_in : in std_logic;
           ps2_data_in : in std_logic;
           ps2_clk_out : out std_logic;
           ps2_data_out : out std_logic);

end ps2_core;

architecture Behavioral of ps2_core is

	COMPONENT prescaler
	GENERIC (divide : integer);
	PORT(
		rst : IN std_logic;
		clk : IN std_logic;
		ce : IN std_logic;          
		rdy : OUT std_logic
		);
	END COMPONENT;

	COMPONENT timeout_counter
	GENERIC (N : integer);
	PORT(
		rst : IN std_logic;
		clk : IN std_logic;
		ce : IN std_logic;
		load : IN integer range 0 to N-1;
		load_en : IN std_logic;          
		rdy : OUT std_logic
		);
	END COMPONENT;

	COMPONENT sipo
	GENERIC (N : integer);
	PORT(
		rst : IN std_logic;
		clk : IN std_logic;
		ce : IN std_logic;
		d_in : IN std_logic;          
		d_out : OUT std_logic_vector(7 downto 0)
		);
	END COMPONENT;

	COMPONENT piso
	GENERIC (N : integer);
	PORT(
		rst : IN std_logic;
		clk : IN std_logic;
		ce : IN std_logic;
		load_en : IN std_logic;
		d_in : IN std_logic_vector(7 downto 0);          
		d_out : OUT std_logic
		);
	END COMPONENT;

	COMPONENT parity_bitser
	PORT(
		rst : IN std_logic;
		clk : IN std_logic;
		srst : IN std_logic;
		ce : IN std_logic;
		d_in : IN std_logic;          
		parity : OUT std_logic
		);
	END COMPONENT;

	COMPONENT oneshot
	GENERIC (mode : integer);
	PORT(
		rst : IN std_logic;
		clk : IN std_logic;
		input : IN std_logic;          
		output : OUT std_logic
		);
	END COMPONENT;


-- 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);

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 := f_sys_clk/1E6;
constant timeout_us            : integer := 15000;
constant time_clock_assert     : integer := 120;

-- Signals
signal en_us               : std_logic;
signal timeout_load_en     : std_logic;
signal timeout_en          : std_logic;
signal timeout_flag        : std_logic;

signal timeout_tx_load_en  : std_logic;
signal timeout_tx_en       : std_logic;
signal timeout_tx_flag     : std_logic;
signal timeval_tx          : integer := time_clock_assert-1;

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 tx_busy             : std_logic;
signal rx_rdy              : std_logic;
signal tx_rdy              : std_logic;
signal tx_req              : std_logic;
signal rx_reg              : std_logic_vector(7 downto 0);
signal tx_reg              : std_logic_vector(7 downto 0);

signal parity_rst          : std_logic;
signal parity_en           : std_logic;
signal parity              : std_logic;
signal parity_tx_rst       : std_logic;
signal parity_tx_en        : std_logic;
signal parity_tx           : std_logic;

begin

	Inst_prescaler: prescaler
	GENERIC MAP (
		divide => prescaler_us)		
	PORT MAP(
		rst => rst,
		clk => clk,
		ce => ce,
		rdy => en_us
	);

	Inst_timeout_counter_rx: timeout_counter 
	GENERIC MAP (
		N => timeout_us)		
	PORT MAP(
		rst => rst,
		clk => clk,
		ce => timeout_en,
		load => timeout_us - 1,
		load_en => timeout_load_en,
		rdy => timeout_flag
	);

	Inst_timeout_counter_tx: timeout_counter 
	GENERIC MAP (
		N => timeout_us)		
	PORT MAP(
		rst => rst,
		clk => clk,
		ce => timeout_tx_en,
		load => timeval_tx,
		load_en => timeout_tx_load_en,
		rdy => timeout_tx_flag
	);

	Inst_sipo: sipo
	GENERIC MAP (
		N => 8)		
	PORT MAP(
		rst => rst,
		clk => clk,
		ce => sipo_en,
		d_in => ps2_data_in,
		d_out => rx_reg
	);

	Inst_piso: piso
	GENERIC MAP (
		N => 8)		
	PORT MAP(
		rst => rst,
		clk => clk,
		ce => piso_en,
		load_en => piso_load_en,
		d_in => tx_reg,
		d_out => piso_out
	);

	Inst_parity_bitser_rx: parity_bitser
	PORT MAP(
		rst => rst,
		clk => clk,
		srst => parity_rst,
		ce => parity_en,
		d_in => ps2_data_in,
		parity => parity
	);

	Inst_parity_bitser_tx: parity_bitser
	PORT MAP(
		rst => rst,
		clk => clk,
		srst => parity_tx_rst,
		ce => parity_tx_en,
		d_in => piso_out,
		parity => parity_tx
	);


	-------------------------------------------
	tx_en <= tx_req and (not rx_busy);
	rx_en <= not tx_busy;

	-------------------------------------------
	out_reg: process(rst, clk, rx_rdy, read_en)
	begin
		if rst = '1' then
			d_out      <= (others => '0');
			output_rdy <= '0';
		elsif rising_edge(clk) then
			if rx_rdy = '1' then
				d_out      <= rx_reg;
				output_rdy <= '1';
			elsif	read_en = '1' then
				output_rdy <= '0';
			end if;
		end if;
	end process;

	in_reg: process(rst, clk, piso_load_en, write_en)
	begin
		if rst = '1' then
			input_rdy    <= '1';
			tx_req       <= '0';
		elsif rising_edge(clk) then
			if piso_load_en = '1' then
				tx_reg       <= d_in;
				input_rdy    <= '1';
				tx_req       <= '0';
			elsif	write_en = '1' then
				input_rdy    <= '0';
				tx_req       <= '1';
			end if;
		end if;
	end process;

	-------------------------------------------
	read_state_clk: process(rst, clk)
	begin
		if rst = '1' then
			read_cs <= start_wait_st;
		else
			if rising_edge(clk) then
				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;

	-------------------------------------------
	write_state_clk: process(rst, clk)
	begin
		if rst = '1' then
			write_cs <= tx_idle_st;
		else
			if rising_edge(clk) then
				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_output: process(read_cs, en_us)
	begin
	
		timeout_load_en <= '0';
		timeout_en      <= en_us;
		data_cnt_en     <= '0';
		data_cnt_rst    <= '0';
		sipo_en         <= '0';
		rx_rdy          <= '0';
		parity_en 		 <= '0';
		parity_rst		 <= '0';
		rx_busy         <= '1';
      case (read_cs) is
         when start_wait_st =>
				timeout_load_en <= '1';
				timeout_en      <= '0';
				rx_busy         <= '0';
         when start_sample_st =>
				data_cnt_rst    <= '1';
				parity_rst		 <= '1';
				timeout_load_en <= '1';
         when data_sample_st =>
				data_cnt_en     <= '1';	
				parity_en 		 <= '1';
				sipo_en			 <= '1';	
				timeout_load_en <= '1';
         when parity_sample_st =>
				timeout_load_en <= '1';
				parity_en 		 <= '1';
         when valid_st =>
				rx_busy         <= '0';
				rx_rdy          <= '1';
         when others => null; 
      end case;      
	end process;

   read_state_decode: process (read_cs, timeout_flag, rx_en, data_cnt, ps2_data_in, ps2_clk_in)
   begin
      --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 =>
				if ps2_data_in = '0' and ps2_clk_in = '1' and rx_en = '1' then
               read_ns <= start_trans_st;
            end if;
         when start_trans_st =>
				if timeout_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_data_in = '0' and ps2_clk_in = '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_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_in = '1' then
               read_ns <= data_trans_st;
            end if;
         -- Wait data trans
         when data_trans_st =>
				if timeout_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_in = '0' then
               read_ns <= data_sample_st;
            end if;
         -- Data sample
         when data_sample_st =>
				read_ns <= data_wait_st;
				if data_cnt = 0 then
	            read_ns <= parity_wait_st;
				end if;
         when parity_wait_st =>
				if timeout_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_in = '1' then
               read_ns <= parity_trans_st;
            end if;
         when parity_trans_st =>
				if timeout_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_in = '0' then
               read_ns <= parity_sample_st;
            end if;
         when parity_sample_st =>
				read_ns <= stop_wait_st;
         when stop_wait_st =>
				if timeout_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_in = '1' then
               read_ns <= stop_trans_st;
            end if;
         when stop_trans_st =>
				if timeout_flag = '1' then
               read_ns <= start_wait_st;
            elsif ps2_clk_in = '0' and ps2_data_in = '1' then
               read_ns <= stop_sample_st;
            end if;
         when stop_sample_st =>
				if timeout_flag = '1' then
               read_ns <= start_wait_st;
				elsif ps2_clk_in = '1' then
               read_ns <= valid_st;
            end if;
         when valid_st =>
            read_ns <= start_wait_st;
         when others =>
            read_ns <= start_wait_st;
      end case;      
   end process;

	-------------------------------------------
	write_state_output: process(write_cs, en_us, piso_out, parity_tx)
	begin
	
		timeout_tx_load_en <= '0';
		timeout_tx_en      <= en_us;
		tx_rdy             <= '0';
		tx_busy            <= '1';
		piso_load_en		 <= '0';
      ps2_clk_out        <= '1';
      ps2_data_out       <= '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';
				timeout_tx_en      <= '0';
         when tx_clock_assert_st =>
				parity_tx_rst      <= '1';
				piso_load_en		 <= '1';
				data_cnt_tx_rst    <= '1';
		      ps2_clk_out        <= '0';
         when tx_start_assert_st =>
		      ps2_clk_out        <= '0';
            ps2_data_out       <= '0';
         when tx_clock_release_st =>
            ps2_data_out       <= '0';
				timeout_tx_load_en <= '1';
				timeval_tx         <=  timeout_us - 1;
         when tx_dev_wait_st =>
            ps2_data_out       <= '0';
         when tx_dev_trans_st =>
            ps2_data_out       <= '0';
				timeout_tx_load_en <= '1';
				timeval_tx         <=  timeout_us - 1;
         when tx_data_wait_st => 
				ps2_data_out       <= piso_out;
         when tx_data_trans_st => 
				data_cnt_tx_en		 <= '1';
				ps2_data_out       <= piso_out;
				piso_en            <= '1';
				timeout_tx_load_en <= '1';
				timeval_tx         <=  timeout_us - 1;
				parity_tx_en       <= '1';
         when tx_data_sample_st => 
				ps2_data_out       <= piso_out;
         when tx_parity_update_st => 
				parity_tx_en       <= '1';
				ps2_data_out       <= piso_out;
         when tx_parity_sample_st => 
				ps2_data_out       <= parity_tx;
         when tx_parity_wait_st => 
				ps2_data_out       <= parity_tx;
         when tx_parity_trans_st => 
				ps2_data_out       <= 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_in, ps2_clk_in)
   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_in = '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_in = '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_in = '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_in = '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_in = '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_in = '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_in = '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_in = '0' and ps2_data_in = '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_in = '1' and ps2_data_in = '1' then
	            write_ns <= tx_valid_st;
            end if;
         when others =>
            write_ns <= tx_idle_st;
      end case;      
   end process;

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