PS2 Receiver Module for VHDL

Description

I have several VHDL modules that have been coded to use with the Spartan 3E. My next couple of posts will have various modules followed by a top level design that connects them all together. This module is for a keyboard receiver. Keyboards communicate two ways. They transmit the key strokes in a 11 bit serial transmission. The first bit is a start bit, followed by 8 bits of data, then a parody bit, and finally ends with the stop bit.

Each key is transmitted one at a time. It is up the bios to interpet if keys are pushed in a combination, if a shift key is pushed, or if caps lock is on. Fortunately the ps2 protocol allows an easy way to check if shift is being pushed or not.

The key is pushed and transmits the keyboard code, and then periodically retransmits that code if the key is held down. When the key is released the keyboard will translate the hex value of F0 and then transmits the pressed key again. I use this to set a register if the shift is pushed and when the code gets resent it sets the register to to zero. The state machine for the ps2 receiver then just disregards two data sets sent after F0.

PS2 Rx


library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ps2_rx is 
	port (
		clk, rst : in std_logic;
		ps2d, ps2c : in std_logic;
		rx_en : in std_logic;
		rx_done_tick : out std_logic;
		shift_en : out std_logic;
		dout : out std_logic_vector(7 downto 0)
	);
end ps2_rx;

architecture arch of ps2_rx is
	type statetype is (idle, dps, load);
	signal state_reg, state_next : statetype;
	-- filter
	signal filter_reg, filter_next : std_logic_vector(7 downto 0);
	signal f_ps2c_reg, f_ps2c_next : std_logic;

	signal b_reg, b_next : std_logic_vector(10 downto 0);
	signal n_reg, n_next : unsigned(3 downto 0);
	signal shift_reg, shift_next : std_logic := '0';
	signal last_reg, last_next : std_logic := '0';
	
	signal fall_edge : std_logic;
	
begin

-- filter 
process (clk, rst) 
begin
  if (rst = '1') then
    filter_reg <= (others => '0');
    f_ps2c_reg <= '0';
  elsif (clk'event and clk='1') then
    filter_reg <= filter_next;
    f_ps2c_reg <= f_ps2c_next;
  end if;
end process;


filter_next <= ps2c & filter_reg(7 downto 1);
f_ps2c_next <= '1' when filter_reg = "11111111" else
               '0' when filter_reg = "00000000" else
               f_ps2c_reg;

fall_edge <= f_ps2c_reg and (not f_ps2c_next);

-- registers
process (clk, rst)
begin
	if (rst = '1') then
		state_reg <= idle;
		n_reg <= (others => '0');
		b_reg <= (others => '0');
	elsif (clk'event and clk='1') then
		state_reg <= state_next;
		n_reg <= n_next;
		b_reg <= b_next;
		shift_reg <= shift_next;
		last_reg <= last_next;
	end if;
end process;

-- next-state logic
process(state_reg, n_reg, b_reg, fall_edge, rx_en, ps2d, shift_reg, last_reg)
begin
	rx_done_tick <= '0';
	state_next <= state_reg;
	n_next <= n_reg;
	b_next <= b_reg;
	shift_next <= shift_reg;
	last_next <= last_reg;
  
  case state_reg is 
    when idle =>
      if (fall_edge = '1' and rx_en='1') then 
        --shift in start bit
        b_next <= ps2d & b_reg(10 downto 1);
        n_next <= "1001"; -- set count to 8 again
        state_next <= dps;
      end if;
    when dps => 
      if (fall_edge = '1' ) then
        b_next <= ps2d & b_reg(10 downto 1);
        if (n_reg = 0) then 
          state_next <= load;
        else 
          n_next <= n_reg - 1;
        end if;
      end if;
    when load =>
		-- here we handle if signal f0 and following signal are 
		-- asserted - we don't want to transmit them to dout.
      -- one more state to complete last shift
		state_next <= idle;
		rx_done_tick <= '1';
		if (b_reg(8 downto 1) = x"12" or b_reg(8 downto 1) = x"59") then
			shift_next <= not shift_reg;
			if (last_reg = '1') then
				rx_done_tick <= '1';
				last_next <= '0';
			end if;
		elsif (b_reg(8 downto 1) = "11110000") then
			last_next <= '1';
			rx_done_tick <= '0';
		elsif (last_reg = '1') then
			last_next <= '0';	
			rx_done_tick <= '0';
		end if;
  end case;
end process;

shift_en <= shift_reg;
dout <= b_reg(8 downto 1);

end arch;

Keyboard to ASCII Decoder

There are 2^8 combinations – 256 options available and each keyboard code needs to be decoded to ascii codes. The following is a decoder checks if the registers are set for either caps or shift and determines the ascii output based on keyboard inputs.

key2ascii

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity key2ascii is
	port (
		caps_enabled : in std_logic;
		shift_enabled : in std_logic;
		key_code : in std_logic_vector(7 downto 0);
		ascii_code : out std_logic_vector(7 downto 0)
	);
end key2ascii;	

architecture arch of key2ascii is 
signal ascii_normal, ascii_caps : std_logic_vector(7 downto 0);
begin
	with key_code select
		ascii_normal <=
			"00110000" when "01000101", --0
			"00110001" when "00010110", --1
			"00110010" when "00011110", --2
			"00110011" when "00100110", --3
			"00110100" when "00100101", --4
			"00110101" when "00101110", --5
			"00110110" when "00110110", --6
			"00110111" when "00111101", --7
			"00111000" when "00111110", --8
			"00111001" when "01000110", --9
			
			"01100001" when "00011100", --a
			"01100010" when "00110010", --b
			"01100011" when "00100001", --c
			"01100100" when "00100011", --d
			"01100101" when "00100100", --e
			"01100110" when "00101011", --f
			"01100111" when "00110100", --g
			"01101000" when "00110011", --h
			"01101001" when "01000011", --i
			"01101010" when "00111011", --j
			"01101011" when "01000010", --k
			"01101100" when "01001011", --l
			"01101101" when "00111010", --m
			"01101110" when "00110001", --n
			"01101111" when "01000100", --o
			"01110000" when "01001101", --p
			"01110001" when "00010101", --q
			"01110010" when "00101101", --r
			"01110011" when "00011011", --s
			"01110100" when "00101100", --t
			"01110101" when "00111100", --u
			"01110110" when "00101010", --v
			"01110111" when "00011101", --w
			"01111000" when "00100010", --x
			"01111001" when "00110101", --y
			"01111010" when "00011010", --z
			
			"01100000" when "00001110", --'
			"00101101" when "01001110", ---
			"00111101" when "01010101", --=
			"01011011" when "01010100", --[
			"01011101" when "01011011", --]
			"01011100" when "01011101", --
			"00111011" when "01001100", --;
			"00100111" when "01010010", --
			"00101100" when "01000001", --,
			"00101110" when "01001001", --.
			"00101111" when "01001010", --/
			"01111010" when "11110000", -- F0
			
			"00100000" when "00101001", -- space
			"00001101" when "01011010", -- enter
			"00001000" when "01100110", -- backspace
			"00101010" when others; --*			
	
	with key_code select
		ascii_caps <=
			"00101001" when "01000101", --)
			"00010001" when "00010110", --!
			"00100000" when "00011110", --@
			"00010011" when "00100110", --#
			"00010100" when "00100101", --$
			"00100101" when "00101110", --%
			"01011110" when "00110110", --^
			"00100110" when "00111101", --&
			"00101010" when "00111110", --*
			"00101000" when "01000110", --(
			
			"01000001" when "00011100", --A
			"01000010" when "00110010", --B
			"01000011" when "00100001", --C
			"01000100" when "00100011", --D
			"01000101" when "00100100", --E
			"01000110" when "00101011", --F
			"01000111" when "00110100", --G
			"01001000" when "00110011", --H
			"01001001" when "01000011", --I
			"01001010" when "00111011", --J
			"01001011" when "01000010", --K
			"01001100" when "01001011", --L
			"01001101" when "00111010", --M
			"01001110" when "00110001", --N
			"01001111" when "01000100", --O
			"01010000" when "01001101", --P
			"01010001" when "00010101", --Q
			"01010010" when "00101101", --R
			"01010011" when "00011011", --S
			"01010100" when "00101100", --T
			"01010101" when "00111100", --U
			"01010110" when "00101010", --V
			"01010111" when "00011101", --W
			"01011000" when "00100010", --X
			"01011001" when "00110101", --Y
			"01011010" when "00011010", --Z
			
			"00111111" when "01001010", --?
			
			"00100000" when "00101001", -- space
			"00001101" when "01011010", -- enter
			"00001000" when "01100110", -- backspace
			"00101010" when others; --*  
		
	ascii_code <= ascii_normal when (caps_enabled = '0' and shift_enabled = '0') else
					  ascii_caps when (caps_enabled = '0' and shift_enabled = '1') else
--					  ascii_caps when (caps_enabled = '1' and shift_enabled = '0') else
					  ascii_normal;
end arch;

2 Responses

Subscribe to comments with RSS.

  • peter dibal says:

    Good day sir. I have followed your code closely and I am really impressed.
    Sir, I want to ur vhdl code for PS2 receiver module. However I can’t find the top module. I will be grateful if you can help me with the link to the top module.

    Warm regards,
    Peter

    • zaphinath says:

      Hi Peter,

      I have looked for my old code, but I cannot seem to find it. If you need a top level module you can use xilinx SDK to generate a new project and just call it top level. Then all you need to do is add the other two vhd modules to the top level, and wire them up. I don’t really use VHDL anymore.