Electrical – VHDL: Detecting key pressed on PS/2 keyboard in FPGA

fpgaps2vhdl

Is it possible to detect a keypress on a PS/2 keyboard connected to an FPGA, using VHDL, with only PS/2 clock signal?

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

entity Keyboard_Drive is 
    Port  ( PS2_DAT, PS2_CLK, clk, rstn : in std_logic;
end entity; 


architecture rtl of Keyboard_Drive is 
    signal PS2_CLK2, PS2_CLK2_old, PS2_DAT2, detected_fall : std_logic;
    signal shiftreg: std_logic_vector (9 downto 0);
    signal pressed: std_logic := '0';
begin 

    input_signals : process (clk) begin 
        if rising_edge (clk) then 
            -- get data
            PS2_DAT2 <= PS2_DAT;
            PS2_CLK2 <= PS2_CLK;
            PS2_CLK2_old <= PS2_CLK2;
        end if;
    end process;

    detected_fall <= (NOT PS2_CLK2) AND PS2_CLK2_old;

    Key: process (clk, rstn) begin 
        if rstn = '0' then 
            shiftreg <= (others => '0');

        elsif rising_edge (clk) then
            -- assign shift
            if detected_fall = '1' then
                shiftreg (8 downto 0)   <= shiftreg (9 downto 1);
                shiftreg (9)            <= PS2_DAT2;
            end if;
        end if;
    end process;

I was considering to add this process (code under) to try to detect only when PS2_CLK rises, because I read that PS2_CLK is constantly high (PS2_CLK=1) when they keyboard is not in use.

sound : process (clk) is 
begin
    if rising_edge(PS2_CLK2)  then
            pressed <= '1'; 
    else 
            pressed <= '0';
    end if;
end process;

But this gives an error ('couldn't implement registers for assignments on this clock edge').

I have tried to read and understand bouncing and de-bouncing and how to take it into consideration. I have tried many solutions and many hours. It felt as a simple problem in the beginning but it never gets solved.

The pressed signal has to be a steady '1' when being pressed since the signal will be used for producing sound when being pressed.

Thanks in advance.

Best Answer

There's a lot of good PS/2 info here. This does mention using debounce. I'm not sure how important that is, as the keyboard will likely have some amount of debounce before sending a code via PS/2 for you to process in VHDL.

What you are trying to do in the sound process is making assignment on rising_edge(PS2_CLK2), but also making assignment when NOT rising_edge(PS2_CLK2) which isn't really valid. I believe that is the source of your error. The idea of setting depressed to 0 any other time than a rising edge wouldn't really work anyway, as you said you want a steady '1' when pressed. If this circuit existed which could check rising_edge() and also NOT rising_edge(), your depressed signal would never be steady, as it would get set only at the instant of a rising edge and never any other time.

The PS/2 clock will toggle multiple times while a code is sent. One approach would be having a timer that resets on any PS/2 clock edge. While the timer is greater than zero, depressed is asserted. If a PS/2 clock edge is detected, the timer is reset to the max time for a high or low PS/2 clock pulse. The Digikey link I gave says the PS/2 clock period is between 60-100us, so you would set your timer value to 50us or 1/2 the max clock period you might have. If you don't detect an edge in this amount of time, a key is no longer being pressed, and depressed gets deasserted.

A final note, since your PS/2 signals are in a separate clock domain to your system clock, the signals need to be double-registered to mitigate metastability. This means you need an additional register on your PS2_DAT and PS2_CLK signals before using. I.e.:

    signal PS2_CLK2,PS2_CLK3,PS3_CLK4,PS2_DAT2,PS2_DAT3, detected_fall : std_logic;

    ...

    input_signals : process (clk) begin 
      if rising_edge (clk) then 
        -- get data
        PS2_DAT2 <= PS2_DAT;  --PS2_DAT2 is NOT properly synchronized. Don't use.
        PS2_DAT3 <= PS2_DAT2; --PS2_DAT3 is properly synchronized. OK to use.
        PS2_CLK2 <= PS2_CLK;  --PS2_CLK2 is NOT properly synchronized. Don't use.
        PS2_CLK3 <= PS2_CLK2; --PS2_CLK3 is properly synchronized. OK to use.
        PS2_CLK4 <= PS2_CLK3; --PS2_CLK4 is properly synchronized. OK to use.
      end if;
    end process;

    ...

    detected_fall <= (NOT PS2_CLK3) AND PS2_CLK4; --Use only properly synchronized signals

    ...

    Key: process (clk, rstn) begin 
      if rstn = '0' then 
        shiftreg <= (others => '0');
      elsif rising_edge (clk) then
        -- assign shift
        if detected_fall = '1' then
            shiftreg (8 downto 0)   <= shiftreg (9 downto 1);
            shiftreg (9)            <= PS2_DAT3; --Use only properly synchronized signals
        end if;
      end if;
    end process;

By the way, for generic (rising or falling) edge detect, you would do:

    PS2_CLK_EDGE <= PS2_CLK4 XOR PS2_CLK3;
Related Topic