-- -- Copyright (C) 2003 by J. Kearney, Bolton, Massachusetts -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, but -- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -- for more details. -- -- You should have received a copy of the GNU General Public License along -- with this program; if not, write to the Free Software Foundation, Inc., -- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.numeric_std.ALL; library unisim; use unisim.vcomponents.all; entity ps2kbd is Port ( reset: in boolean; clk: in std_logic; -- logic clock clkdiv24: in std_logic; -- ~1MHz clock alt_keypad_mode: in boolean; hold_screen_mode: in boolean; send_identify: in boolean; hold_line_release: out std_logic; hold_scrn_release: out std_logic; break_key: out std_logic; ack: in std_logic; -- active one logic clock data: out std_logic_vector(6 downto 0); -- active until ack data_avail: out std_logic; -- active until ack kb_clk_raw: inout std_logic; -- to ps/2 keyboard kb_data_raw: inout std_logic); -- " " " end ps2kbd; architecture RTL of ps2kbd is -- this component deglitches the input component Voter is port (reset: in boolean; clk: in std_logic; inp: in std_logic; outp : out std_logic); end component; attribute INIT_00: string; attribute INIT_01: string; attribute INIT_02: string; attribute INIT_03: string; attribute INIT_04: string; attribute INIT_05: string; attribute INIT_06: string; attribute INIT_07: string; attribute INIT_08: string; attribute INIT_09: string; attribute INIT_0A: string; attribute INIT_0B: string; attribute INIT_0C: string; attribute INIT_0D: string; attribute INIT_0E: string; attribute INIT_0F: string; -- PC/AT keycode lookup table. Encoding is: -- 4 bytes for each code (right to left) -- in each code, from left to right: -- unshifted result -- Ctrl result -- Shift result -- Extended result (code preceded by E0 code) -- -- -- Lookup value is -- 0 c c c c c c c => c -- 1 0 c c c c c c => c normal keypad mode - determined by flag -- 1 0 c c c c c c => ESC ? c+@ alt keypad mode / -- 1 1 0 c c c c c => ESC c+@ -- 1 1 1 0 0 0 0 0 E0 => dead key -- 1 1 1 0 0 0 0 1 E1 => shift -- 1 1 1 0 0 0 1 0 E2 => ctrl -- 1 1 1 0 0 1 0 0 E4 => caps lock -- 1 1 1 0 1 0 0 0 E8 => hold screen line release -- 1 1 1 0 1 0 0 1 E9 => hold screen screen release -- 1 1 1 0 1 0 1 0 EA => break -- -- 00-07 |7 |6 |5 |4 |3 |2 |1 |0 | attribute INIT_00 of rom : label is "E0E0E0E0D1D1D1E0D0D0D0E0D2D2D2E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0"; -- 08-0F |F |E |D |C |B |A |9 |8 | attribute INIT_01 of rom : label is "E0E0E0E0601E7EE0090909E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0"; -- 10-1F attribute INIT_02 of rom : label is "E0E0E0E031E021E0711151E0E2E2E2E2E0E0E0E0E1E1E1E1E0E0E0E0E0E0E0E0"; attribute INIT_03 of rom : label is "E0E0E0E0320040E0771757E0610141E0731353E07A1A5AE0E0E0E0E0E0E0E0E0"; -- 20-2F attribute INIT_04 of rom : label is "E0E0E0E033E023E034E024E0650545E0640444E0781858E0630343E0E0E0E0E0"; attribute INIT_05 of rom : label is "E0E0E0E035E025E0721252E0741454E0660646E0761656E0202020E0E0E0E0E0"; -- 30-3F attribute INIT_06 of rom : label is "E0E0E0E036E05EE0791959E0670747E0680848E0620242E06E0E4EE0E0E0E0E0"; attribute INIT_07 of rom : label is "E0E0E0E038E02AE037E026E0751555E06A0A4AE06D0D4DE0E0E0E0E0E0E0E0E0"; -- 40-4F attribute INIT_08 of rom : label is "E0E0E0E039E028E030E029E06F0F4FE0690949E06B0B4BE02CE03CE0E0E0E0E0"; attribute INIT_09 of rom : label is "E0E0E0E02D1F5FE0701050E03BE03AE06C0C4CE02FE03F2F2EE03EE0E0E0E0E0"; -- 50-5F attribute INIT_0A of rom : label is "E0E0E0E0E0E0E0E03D1D2BE05B1B7BE0E0E0E0E027E022E0E0E0E0E0E0E0E0E0"; attribute INIT_0B of rom : label is "E0E0E0E0E0E0E0E05C1C7CE0E0E0E0E05D1D7DE00D0D0D8DE1E1E1E1E4E4E4E4"; -- 60-6F attribute INIT_0C of rom : label is "E0E0E0E07F7F7Fe0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0"; attribute INIT_0D of rom : label is "E0E0E0E0E0E0E0E0E0E0E0E0B7B7E0E0B4B4C4C4E0E0E0E0B1B1E0E0E0E0E0E0"; -- 70-7F attribute INIT_0E of rom : label is "E0EAE0E01B1B1BE0B8B8C1C1B6B6C3C3B5B5E0E0B2B2C2C2AE7FE07FB0B0E0E0"; attribute INIT_0F of rom : label is "E0E0E0E0E8F0E0E0B9B9E0E02A2A2AE02D2D2DE0B3B3E0E02B2B2BE0E0E0E0E0"; signal kb_clk, kb_data: std_logic; -- low-level receive signal sr: std_logic_vector(9 downto 0); signal kb_clk_prev, parity: std_logic; signal RC_1, RC_2: std_logic; signal timeout: integer range 0 to 127; -- ASCII conversion state machine signal break, ctrl, shift, capsl: boolean; signal lookup: std_logic_vector(8 downto 0); signal lookup_res: std_logic_vector(7 downto 0); signal xlate: unsigned(7 downto 0); signal lookup_en: std_logic; -- state machine for keyboard input code sequences type KStates is (Idle, GotExt, GotBrk, GotExtBrk); signal state: KStates; -- state machine for output (CPU) code sequences type CStates is (Idle, Alpha0, -- c KPN, -- c-80 KPA, -- ESC '?' c-80+40 QM3, Alpha3, ESC1, Alpha1, -- ESC c-C0+40 Ident1, -- ESC '/' 'K' Ident2, Ident3, BreakKey, HoldReleaseScreen, HoldReleaseLine); signal cstate: CStates; -- state machine for keyboard output sequences type XStates is (InitRead, Reading, Initiate, Initiate_HS, XByte_l, XByte_h, HS_l, HS_h); signal xstate: XStates; signal LEDs, LEDs_prev: std_logic_vector(0 to 2); -- these following types and function are intended to allow -- the optimizer to generate the LED setting sequence as -- efficiently as possible. The previous approach of using -- an array of registers seemed to get optimized, but the -- registers that were then unused were not optimized out, -- according to the warning messages. subtype XBitType is integer range 0 to 9; subtype KByte is std_logic_vector(XBitType); subtype XByteType is integer range 0 to 1; signal xbit: XBitType; signal xbyte: XByteType; impure function LEDCmd(seq: in XByteType; pos: in XBitType) return std_logic is type LEDSequence is array(XByteType) of KByte; constant LEDseq: LEDSequence := ( ( '1', '0', '1', '1', '0', '1', '1', '1', '1', '1' ), ( '0', '0', '0', '0', '0', '0', '0', '0', '0', '1' ) ); begin if seq = 1 then case pos is when 0 => return LEDs(0); when 1 => return LEDs(1); when 2 => return LEDs(2); when 8 => return not (LEDs(0) xor LEDs(1) xor LEDs(2)); when others => return LEDseq(seq)(pos); end case; else return LEDseq(seq)(pos); end if; end LEDCmd; begin vote_clk: Voter port map (reset, clkdiv24, kb_clk_raw, kb_clk); vote_data: Voter port map (reset, clkdiv24, kb_data_raw, kb_data); rom: RAMB4_S8 port map (WE => '0', EN => lookup_en, RST => '0', CLK => clk, ADDR => lookup, DI => "00000000", DO => lookup_res); lookup_en <= '1' when (cstate = Idle) else '0'; lookup(8 downto 2) <= sr(6 downto 0); lookup(1 downto 0) <= "00" when (state = GotExt) or (state = GotExtBrk) else "10" when ctrl else "01" when shift else "11"; xlate <= unsigned(lookup_res); process (cstate, xlate, capsl) begin case cstate is when Alpha0 => -- check for Caps Lock on, if so shift if alpha only if capsl and (xlate(6 downto 5) = "11") and (xlate(4 downto 0) /= "00000") and (xlate(4 downto 0) <= "11010") then data <= std_logic_vector(xlate(6 downto 0) + "1100000"); else data <= std_logic_vector(xlate(6 downto 0)); end if; when KPN | Alpha1 => data <= std_logic_vector(xlate(6 downto 0)); when ESC1 | KPA | Ident1 => data <= "0011011"; -- char or "?" char or "/K" when QM3 => data <= "0111111"; -- "?" when Alpha3 => data <= std_logic_vector(xlate(6 downto 0) + "1000000"); when Ident2 => data <= "0101111"; -- "/" when Ident3 => data <= "1001011"; -- "K" when others => data <= (others => '0'); end case; end process; data_avail <= '1' when cstate /= Idle else '0'; hold_line_release <= '1' when cstate = HoldReleaseLine else '0'; hold_scrn_release <= '1' when cstate = HoldReleaseScreen else '0'; break_key <= '1' when cstate = BreakKey else '0'; LEDs(0) <= '1' when hold_screen_mode else '0'; LEDs(1) <= '1' when alt_keypad_mode else '0'; LEDs(2) <= '1' when capsl else '0'; break <= (state = GotBrk) or (state = GotExtBrk); process (reset, clk, RC_1, RC_2) begin if reset then RC_1 <= '0'; state <= Idle; cstate <= Idle; ctrl <= false; shift <= false; capsl <= true; elsif rising_edge(clk) then -- check for end of read strobe from host; if true, -- advance read state if ack = '1' then case cstate is when ESC1 => cstate <= Alpha1; when KPA => cstate <= QM3; when QM3 => cstate <= Alpha3; when Ident1 => cstate <= Ident2; when others => cstate <= Idle; end case; end if; -- check for identify sequence if send_identify then cstate <= Ident1; -- check for new code from keyboard elsif RC_1 /= RC_2 then RC_1 <= RC_2; case sr(7 downto 0) is when "11100000" => -- ext if state = Idle then state <= GotExt; else state <= Idle; end if; when "11110000" => -- break if state = Idle then state <= GotBrk; elsif state = GotExt then state <= GotExtBrk; else state <= Idle; end if; when "1-------" => -- command ack, or other unhandled code, ignore null; when others => -- a keypress, use lookup value or modify shift status state <= Idle; if not break then if xlate(7) = '0' then cstate <= Alpha0; elsif xlate(6) = '0' then if alt_keypad_mode then cstate <= KPA; else cstate <= KPN; end if; elsif xlate(5) = '0' then cstate <= ESC1; elsif xlate(4 downto 3) = "01" then case xlate(2 downto 0) is when "000" => cstate <= HoldReleaseLine; when "001" => cstate <= HoldReleaseScreen; when "010" => -- break key cstate <= BreakKey; when others => null; end case; end if; end if; if xlate(7 downto 5) = "111" then if break then if xlate(0) = '1' then -- shift shift <= false; end if; if xlate(1) = '1' then -- ctrl ctrl <= false; end if; else -- make if xlate(3) = '0' then if xlate(0) = '1' then -- shift shift <= true; end if; if xlate(1) = '1' then -- ctrl ctrl <= true; end if; if xlate(2) = '1' then -- caps capsl <= not capsl; end if; end if; end if; end if; end case; end if; end if; end process; process (reset, clkdiv24, kb_clk_prev, kb_data, RC_1, LEDs) begin if reset then RC_2 <= '0'; kb_clk_prev <= '0'; xstate <= InitRead; LEDs_prev <= (others => '0'); elsif rising_edge(clkdiv24) then kb_clk_prev <= kb_clk; case xstate is when InitRead => kb_clk_raw <= 'Z'; kb_data_raw <= 'Z'; timeout <= 0; parity <= '0'; xstate <= Reading; when Reading => -- if idle, and LEDs should change, send out the update command if (LEDs /= LEDs_prev) and (timeout = 0) then LEDs_prev <= LEDs; xbyte <= XByteType'left; xstate <= Initiate; kb_clk_raw <= '0'; timeout <= 127; end if; if (kb_clk = '0') and (kb_clk_prev = '1') then sr(8 downto 0) <= sr(9 downto 1); sr(9) <= kb_data; timeout <= 127; parity <= parity xor kb_data; else if (timeout = 1) and (parity = '0') and (sr(9) = '1') then RC_2 <= not RC_1; parity <= '0'; end if; if timeout /= 0 then timeout <= timeout - 1; end if; end if; when Initiate => if timeout = 0 then kb_clk_raw <= 'Z'; kb_data_raw <= '0'; xstate <= Initiate_HS; else timeout <= timeout - 1; end if; when Initiate_HS => if kb_clk = '1' then xbit <= XBitType'left; xstate <= XByte_l; end if; when XByte_l => if kb_clk = '0' then kb_data_raw <= LEDCmd(xbyte, xbit); xstate <= XByte_h; end if; when XByte_h => if kb_clk = '1' then if xbit = XBitType'right then xstate <= HS_l; kb_data_raw <= 'Z'; timeout <= 127; else xbit <= xbit + 1; xstate <= XByte_l; end if; end if; when HS_l => if (kb_data = '0') and (kb_clk = '0') then xstate <= HS_h; else if timeout = 0 then xstate <= InitRead; else timeout <= timeout - 1; end if; end if; when HS_h => if (kb_data = '1') and (kb_clk = '1') then if xbyte /= XByteType'right then xbyte <= xbyte + 1; xstate <= Initiate; kb_clk_raw <= '0'; timeout <= 127; else xstate <= InitRead; end if; end if; end case; end if; end process; end RTL;