Zur Eingabe von Daten bietet sich eine PS/2 Tastatur an. Die Dinger gibts für ein paar Euros und das Protokoll ist in der einfachsten Variante sehr überschaubar.
Ich habe hier mehrere Implementationen: die Erste ist sehr einfach gehalten und gibt alle empfangenen Scancodes incl. Break und Extended Keycodes einfach mit einem Handshake weiter an ein übergeordnetes Modul:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity PS2_Keyboard is
Port ( PS2_Data : in std_logic;
PS2_Clk : in std_logic;
RxData : out std_logic_vector(7 downto 0);
RxActive : out std_logic;
DataReady : out std_logic; -- Handshake-Signal: Daten bereit
DataFetched : in std_logic; -- Handshake-signal: Daten übernommen
CLK : in std_logic);
end PS2_Keyboard;
architecture Behavioral of PS2_Keyboard is
signal RxTimeout : integer range 0 to 500000 := 0; -- max. 10ms
signal RxRegister : std_logic_vector(10 downto 0) := (others=>'1');
signal DataSR : std_logic_vector(1 downto 0) := (others=>'1');
signal ClkSR : std_logic_vector(1 downto 0) := (others=>'1');
type PS2RXStateType is (IDLE, RECEIVE, READY);
signal PS2RXState : PS2RXStateType := IDLE;
begin
process begin
wait until rising_edge(CLK);
RxTimeout <= RxTimeout+1;
DataSR <= DataSR(0) & PS2_Data;
ClkSR <= ClkSR(0) & PS2_Clk;
if (ClkSR = "10") then -- fallende Flanke am PS2_CLK
RxRegister <= DataSR(1) & RxRegister(10 downto 1);
end if;
case PS2RXState is
when IDLE =>
RxRegister <= (others=>'1');
RxActive <= '0';
DataReady <= '0';
RxTimeout <= 0;
if (DataSR(1) = '0' and ClkSR(1) = '1') then -- STARTBIT erkannt
PS2RXState <= RECEIVE;
RxActive <= '1';
end if;
when RECEIVE =>
if (RxTimeout = 500000) then -- 10 ms
PS2RXState <= IDLE;
elsif (RxRegister(0) = '0') then -- Startbit ganz durchgeschoben?
DataReady <= '1'; -- SCANCODE empfangen
RxData <= RxRegister(8 downto 1);
PS2RXState <= READY;
end if;
when READY => -- warten, bis Daten abgeholt sind
if (DataFetched='1') then
PS2RXState <= IDLE;
DataReady <= '0';
RxActive <= '0';
end if;
end case;
end process;
end Behavioral;
Realisiert wurde dieses PS/2 Interface in Form eines 11 Bit breiten Schieberegisters, das zu Beginn im Zustand IDLE mit '1'en vorbelegt wird. Mit jeder fallenden Flanke am PS2-Clk wird dieses Schieberegister um 1 Bit von links her weitergeschoben. In das MSB wird dabei der Zustand des einsynchronisierten PS2-Data Pins eingetragen.
Nachdem ein Startbit erkannt wurde, wechselt der Zustand auf RECEIVE und es werden solange Bits eingeschoben, bis das Startbit das rechte Ende (Bit0) des Schieberegisters erreicht hat. Dann werden die Daten in das Pufferregister übernommen und im Zustand READY gewartet, bis die Daten abgeholt sind.
Zusammen mit dieser Testbench:
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
ENTITY PS2_tb IS
END PS2_tb;
ARCHITECTURE behavior OF PS2_tb IS
procedure PS2Send( signal din : in std_logic_vector(7 downto 0);
signal ps2c : OUT std_logic;
signal ps2d : OUT std_logic) is
variable parity : std_logic := '1';
begin
wait for 1 ns;
ps2c <= '1';
ps2d <= '1';
wait for 5us;
ps2d <= '0'; -- startbit
wait for 20us;
ps2c <= '0';
wait for 40us;
ps2c <= '1';
wait for 20us;
parity := '1';
for i in 0 to 7 loop
ps2d <= din(i);
if(din(i)='1') then
parity := not parity;
end if;
wait for 20us;
ps2c <= '0';
wait for 40us;
ps2c <= '1';
wait for 20us;
end loop;
ps2d <= parity; -- parity
wait for 20us;
ps2c <= '0';
wait for 40us;
ps2c <= '1';
wait for 20us;
ps2d <= '1'; -- stopbit
wait for 20us;
ps2c <= '0';
wait for 40us;
ps2c <= '1';
wait for 20us;
end procedure;
-- Component Declaration for the Unit Under Test (UUT)
COMPONENT PS2_Keyboard
PORT(
PS2_Data : IN std_logic;
PS2_Clk : IN std_logic;
RxData : OUT std_logic_vector(7 downto 0);
RxActive : OUT std_logic;
DataReady : OUT std_logic;
DataFetched : IN std_logic;
CLK : IN std_logic
);
END COMPONENT;
signal TxData : std_logic_vector(7 downto 0);
--Inputs
signal PS2_Data : std_logic := '1';
signal PS2_Clk : std_logic := '1';
signal DataFetched : std_logic := '0';
signal CLK : std_logic := '0';
--Outputs
signal RxData : std_logic_vector(7 downto 0);
signal RxActive : std_logic;
signal DataReady : std_logic;
-- Clock period definitions
constant CLK_period : time := 20 ns; -- 50MHz
BEGIN
-- Instantiate the Unit Under Test (UUT)
uut: PS2_Keyboard PORT MAP (
PS2_Data => PS2_Data,
PS2_Clk => PS2_Clk,
RxData => RxData,
RxActive => RxActive,
DataReady => DataReady,
DataFetched => DataFetched,
CLK => CLK
);
-- Clock process definitions
CLK <= not CLK after CLK_period/2;
-- Stimulus process
DataFetched <= DataReady after 10 us; -- fake handshake
stim_proc: process
begin
-- hold reset state for 100 ns.
wait for 100 ns;
TXData<=x"AA";
PS2Send(TXData,PS2_Clk,PS2_Data);
wait for 100 us;
TXData<=x"55";
PS2Send(TXData,PS2_Clk,PS2_Data);
wait for 100 us;
TXData<=x"11";
PS2Send(TXData,PS2_Clk,PS2_Data);
wait for 100 us;
TXData<=x"80";
PS2Send(TXData,PS2_Clk,PS2_Data);
wait for 100 us;
TXData<=x"01";
PS2Send(TXData,PS2_Clk,PS2_Data);
wait for 100 us;
TXData<=x"C3";
PS2Send(TXData,PS2_Clk,PS2_Data);
wait for 100 us;
wait;
end process;
END;
ergibt sich dann folgende Waveform:
Hier die zusammengepackte Quelldatei und die passende Testbench:
Download PS2_Simplest.zip
In der zweiten Variante werden die Steuerkommandos (Break und Extended Keycode) in Flags umgewandelt:
Download PS2_Simple.zip
Die dritte Variante schliesslich erlaubt auch das Senden von Kommandos an die Tastatur. Damit können dann z.B. die LEDs auf der Tastatur an- und ausgeschaltet werden.
Download PS2_Full.zip