1
\$\begingroup\$

I need to implement interface to numpad in keyboard. I am totally newbie in Quartus and VHDL language.
I only know that the decoder should return the appropriate key depending on the entered row and column. So if for example the value of the entered row is "3" and the column is "2" then the output should be "5" because it means that the "5" key was pressed.


The layout works properly, but now I need to implement the "numlock" key, which will block reading the keys.
If the "numlock" key is pressed it means that the system should return the correct values ​​from the pressed keys. However, if numlock is off, it means that the whole keyboard should not work and I should get zeros or uninitialized values ​​on the output.

enter image description here


multiplexer.vhd

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

entity multiplexer is
port(
    columns : in std_logic_vector(3 downto 0);
    rows : in std_logic_vector(3 downto 0);
    output_binary_key : out std_logic_vector(7 downto 0);
    is_key_pressed : out std_logic
);
end multiplexer;

architecture data_flow of multiplexer is
signal binary_key : std_logic_vector(7 downto 0);
signal tmp_numlock : std_logic;
signal flag : unsigned(3 downto 0);
begin
    process(columns, rows)
    begin
        binary_key(7 downto 4) <= rows;
        binary_key(3 downto 0) <= columns;      
        if columns = "0000" or rows = "0000" then
            is_key_pressed <= '0';
        else
            is_key_pressed <= '1';
        end if;
    end process;
    output_binary_key <= binary_key;
end data_flow;

converter.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity converter is
    port(
        hex_key : in std_logic_vector(7 downto 0);
        output : out std_logic_vector(7 downto 0)
    );
end converter;
architecture data_flow of converter is
    begin
    with hex_key select
    output <=       x"2F" when "00010010",  -- /
                    x"2A" when "00010011",  -- *
                    x"2D" when "00010100",  -- -
                    x"37" when "00100001",  -- 7
                    x"38" when "00100010",  -- 8
                    x"39" when "00100011",  -- 9
                    x"2B" when "00100100",  -- +
                    x"34" when "00110001",  -- 4
                    x"35" when "00110010",  -- 5
                    x"36" when "00110011",  -- 6
                    x"31" when "01000001",  -- 1
                    x"32" when "01000010",  -- 2
                    x"33" when "01000011",  -- 3
                    x"0D" when "01000100",  -- enter
                    x"30" when "01010001",  -- 0
                    x"2E" when "01010011",  -- .
                    x"00" when others;  
end data_flow;

Can anyone tell me how should I implement this "numlock" key now?



EDIT enter image description here

\$\endgroup\$
4
  • 2
    \$\begingroup\$ As a side note, your implementation for reading the keypad doesn't look like it will work for any actual 4x4 keypad array I've ever seen. You are reading a set of rows and a set of columns, but in every 4x4 keypad array I've ever seen (or built) you need to actually scan the rows (drive them one at a time) to read the columns or vice versa. See here for an example of what I'm talking about: learn.parallax.com/tutorials/language/propeller-c/… \$\endgroup\$
    – Mr. Snrub
    Jul 26, 2020 at 21:54
  • \$\begingroup\$ @Mr.Snrub I thought the same thing, but he says it's working. He might be using a keypad with a built-in IC that simply returns the data. \$\endgroup\$
    – Uncle Dino
    Jul 26, 2020 at 22:03
  • \$\begingroup\$ @Sasszem I am interpreting the word "working" to mean "my testbench operates successfully" ;-) \$\endgroup\$
    – Mr. Snrub
    Jul 26, 2020 at 22:06
  • \$\begingroup\$ Haha, of course you are right that "works" is a very limited word for me. Anyway, thanks a lot for the advice, somehow I managed to implement the advice from Sasszem. I have included my final layout in the post edition. \$\endgroup\$
    – Brarord
    Jul 27, 2020 at 12:38

2 Answers 2

3
\$\begingroup\$

Based upon Sasszem's advice, here is a VHDL implementation with synchronisers, debouncers and an internal state within Multiplexer for remembering the status of NUM LOCK which Converter uses to select the correct conversion. The simulation shows the ASCII output in both unsigned format and as a character.

Synchroniser

Synchronises the input (row and column signals) to the system clock.

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

entity Sync is
    generic
    (
        SYNC_BITS: positive := 3;  -- Number of bits in the synchronisation buffer (2 minimum).
        BIT_WIDTH: positive := 8
    );
    port
    (
        clock : in std_logic;
        input : in std_logic_vector(BIT_WIDTH - 1 downto 0);  -- Asynchronous input.
        output: out std_logic_vector(BIT_WIDTH - 1 downto 0)  -- Synchronous output.
    );
end entity;

architecture V1 of Sync is

    constant SYNC_BUFFER_MSB: positive := SYNC_BITS - 1;
    subtype TSyncBuffer is std_logic_vector(SYNC_BUFFER_MSB downto 0);
    type TSyncBuffers is array(0 to BIT_WIDTH - 1) of TSyncBuffer;
    signal sync_buffers: TSyncBuffers;

begin

    assert SYNC_BITS >= 2 report "Need a minimum of 2 bits in the synchronisation buffer.";

    process(all)
    begin
        if rising_edge(clock) then
            for i in 0 to BIT_WIDTH - 1 loop
                sync_buffers(i) <= sync_buffers(i)(SYNC_BUFFER_MSB - 1 downto 0) & input(i);
            end loop;
        end if;
        for i in 0 to BIT_WIDTH - 1 loop
            output(i) <= sync_buffers(i)(SYNC_BUFFER_MSB);
        end loop;
    end process;

end architecture;

Debouncer

Debounces the synchronised input (row and column signals), then outputs a change strobe when it is successfully debounced. This strobe is used to trigger Multiplexer to process the key press.

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

entity Debounce is
    generic
    (
        CLOCK_PERIOD   : time := 20 ns;
        DEBOUNCE_PERIOD: time := 20 ms;  -- Rule of thumb for a keyboard button.
        BIT_WIDTH      : positive := 8
    );
    port
    (
        clock : in std_logic;
        input : in std_logic_vector(BIT_WIDTH - 1 downto 0);                        -- Asynchronous and noisy input.
        output: out std_logic_vector(BIT_WIDTH - 1 downto 0) := (others => '0');    -- Synchronised, debounced and filtered output.
        change: out std_logic := '0'                                                -- Goes high for 1 clock cycle on change of input.
    );
end entity;

architecture V1 of Debounce is

    constant MAX_COUNT: natural := DEBOUNCE_PERIOD / CLOCK_PERIOD - 1;
    signal counter: natural range 0 to MAX_COUNT := 0;  -- Specify the range to reduce number of bits that are synthesised.

begin

    process(all)
        variable change_internal: std_logic := '0';
    begin
        if rising_edge(clock) then
            counter <= 0;   -- Freeze the counter by default to reduce switching losses when input equals output.
            change <= '0';  -- Goes high for 1 clock cycle on change of input.

            if counter = MAX_COUNT then     -- If successfully debounced, notify what happened.
                output <= input;
                change <= change_internal;  -- Goes high for 1 clock cycle on change of input.
            elsif input /= output then      -- Hysteresis.
                counter <= counter + 1;         -- Only increment when input and output differ.
            end if;
        end if;

        -- Change detection.
        if input /= output then
            change_internal := '1';
        else
            change_internal := '0';
        end if;
    end process;

end architecture;

Multiplexer

Added a key pressed strobe to trigger processing the key. Toggles the NUM LOCK status if the pressed key is the NUM LOCK key. This status is used by Converter to convert the key to ASCII.

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

entity Multiplexer is
    port
    (
        reset: in std_logic;
        clock: in std_logic;
        key_pressed_strobe: in std_logic;
        columns : in std_logic_vector(3 downto 0);
        rows : in std_logic_vector(3 downto 0);
        binary_key : out std_logic_vector(7 downto 0);
        is_key_pressed : out std_logic;
        is_num_lock_on : out std_logic
    );
end Multiplexer;

architecture data_flow of Multiplexer is
    signal is_key_pressed_internal : std_logic;

begin
    process(all)
    begin
        if reset then
            binary_key <= "00000000";
            is_key_pressed <= '0';
            is_num_lock_on <= '0';
        elsif rising_edge(clock) then
            if key_pressed_strobe then
                binary_key <= rows & columns;
                is_key_pressed <= is_key_pressed_internal;
                if columns = "0001" and rows = "0001" then
                    is_num_lock_on <= not is_num_lock_on;
                end if;
            end if;
        end if;

        if columns = "0000" or rows = "0000" then
            is_key_pressed_internal <= '0';
        else
            is_key_pressed_internal <= '1';
        end if;
    end process;
end data_flow;

Converter

Added a signal is_num_lock_on to indicate the NUM LOCK status. Added a couple of subtypes called TKey and TAscii with some constants for each key to make the code more readable and maintainable.

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

entity Converter is
    port
    (
        hex_key : in std_logic_vector(7 downto 0);
        is_num_lock_on : in std_logic;
        output : out std_logic_vector(7 downto 0)
    );
end Converter;

architecture data_flow of Converter is
    subtype TKey is std_logic_vector(7 downto 0);
    subtype TAscii is std_logic_vector(7 downto 0);

    constant KEY_NUMLOCK  : TKey := "00010001";  -- Num Lock
    constant KEY_0        : TKey := "01010001";  -- 0
    constant KEY_1        : TKey := "01000001";  -- 1
    constant KEY_2        : TKey := "01000010";  -- 2
    constant KEY_3        : TKey := "01000011";  -- 3
    constant KEY_4        : TKey := "00110001";  -- 4
    constant KEY_5        : TKey := "00110010";  -- 5
    constant KEY_6        : TKey := "00110011";  -- 6
    constant KEY_7        : TKey := "00100001";  -- 7
    constant KEY_8        : TKey := "00100010";  -- 8
    constant KEY_9        : TKey := "00100011";  -- 9
    constant KEY_MULTIPLY : TKey := "00010011";  -- *
    constant KEY_DIVIDE   : TKey := "00010010";  -- /
    constant KEY_PLUS     : TKey := "00100100";  -- +
    constant KEY_SUBTRACT : TKey := "00010100";  -- -
    constant KEY_DOT      : TKey := "01010011";  -- .
    constant KEY_ENTER    : TKey := "01000100";  -- Enter

    constant ASCII_0        : TAscii := x"30"; -- 0
    constant ASCII_1        : TAscii := x"31"; -- 1
    constant ASCII_2        : TAscii := x"32"; -- 2
    constant ASCII_3        : TAscii := x"33"; -- 3
    constant ASCII_4        : TAscii := x"34"; -- 4
    constant ASCII_5        : TAscii := x"35"; -- 5
    constant ASCII_6        : TAscii := x"36"; -- 6
    constant ASCII_7        : TAscii := x"37"; -- 7
    constant ASCII_8        : TAscii := x"38"; -- 8
    constant ASCII_9        : TAscii := x"39"; -- 9
    constant ASCII_MULTIPLY : TAscii := x"2A"; -- *
    constant ASCII_DIVIDE   : TAscii := x"2F"; -- /
    constant ASCII_PLUS     : TAscii := x"2B"; -- +
    constant ASCII_SUBTRACT : TAscii := x"2D"; -- -
    constant ASCII_DOT      : TAscii := x"2E"; -- .
    constant ASCII_ENTER    : TAscii := x"0D"; -- Enter
    constant ASCII_NULL     : TAscii := x"00"; -- Null

begin
    process(all)
    begin
        if is_num_lock_on then
            case hex_key is
                when KEY_0        => output <= ASCII_0;
                when KEY_1        => output <= ASCII_1;
                when KEY_2        => output <= ASCII_2;
                when KEY_3        => output <= ASCII_3;
                when KEY_4        => output <= ASCII_4;
                when KEY_5        => output <= ASCII_5;
                when KEY_6        => output <= ASCII_6;
                when KEY_7        => output <= ASCII_7;
                when KEY_8        => output <= ASCII_8;
                when KEY_9        => output <= ASCII_9;
                when KEY_MULTIPLY => output <= ASCII_MULTIPLY;
                when KEY_DIVIDE   => output <= ASCII_DIVIDE;
                when KEY_PLUS     => output <= ASCII_PLUS;
                when KEY_SUBTRACT => output <= ASCII_SUBTRACT;
                when KEY_DOT      => output <= ASCII_DOT;
                when KEY_ENTER    => output <= ASCII_ENTER;
                when others       => output <= ASCII_NULL;
            end case;
        else
            case hex_key is
                when KEY_MULTIPLY => output <= ASCII_MULTIPLY;
                when KEY_DIVIDE   => output <= ASCII_DIVIDE;
                when KEY_PLUS     => output <= ASCII_PLUS;
                when KEY_SUBTRACT => output <= ASCII_SUBTRACT;
                when KEY_DOT      => output <= ASCII_DOT;
                when KEY_ENTER    => output <= ASCII_ENTER;
                when others       => output <= ASCII_NULL;
            end case;
        end if;
    end process;
end data_flow;

Test Bench

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

entity Keypad_TB is
end;

architecture V1 of Keypad_TB is

    constant CLOCK_PERIOD   : time := 50 ns;
    constant DEBOUNCE_PERIOD: time := 200 ns;  -- Use ~20 ms for actual keypad.

    signal halt_sys_clock: boolean := false;

    signal reset: std_logic := '0';
    signal clock: std_logic := '0';
    signal input: std_logic_vector(7 downto 0);
    signal input_sync: std_logic_vector(7 downto 0);
    signal input_sync_db: std_logic_vector(7 downto 0);
    alias rows is input_sync_db(7 downto 4);
    alias columns is input_sync_db(3 downto 0);
    signal change: std_logic;
    signal key: std_logic_vector(7 downto 0);
    signal ascii: std_logic_vector(7 downto 0);
    signal is_key_pressed: std_logic;
    signal is_num_lock_on: std_logic;

    component Sync is
        generic
        (
            SYNC_BITS: positive := 3;  -- Number of bits in the synchronisation buffer (2 minimum).
            BIT_WIDTH: positive := 8
        );
        port
        (
            clock : in std_logic;
            input : in std_logic_vector(BIT_WIDTH - 1 downto 0);  -- Asynchronous input.
            output: out std_logic_vector(BIT_WIDTH - 1 downto 0)  -- Synchronous output.
        );
    end component;

    component Debounce is
        generic
        (
            CLOCK_PERIOD   : time := 20 ns;
            DEBOUNCE_PERIOD: time := 20 ms;  -- Rule of thumb for a keyboard button.
            BIT_WIDTH      : positive := 8
        );
        port
        (
            clock : in std_logic;
            input : in std_logic_vector(BIT_WIDTH - 1 downto 0);                        -- Asynchronous and noisy input.
            output: out std_logic_vector(BIT_WIDTH - 1 downto 0) := (others => '0');    -- Synchronised, debounced and filtered output.
            change: out std_logic := '0'                                                -- Goes high for 1 clock cycle on change of input.
        );
    end component;

    component Multiplexer is
        port
        (
            reset: in std_logic;
            clock: in std_logic;
            key_pressed_strobe: in std_logic;
            columns : in std_logic_vector(3 downto 0);
            rows : in std_logic_vector(3 downto 0);
            binary_key : out std_logic_vector(7 downto 0);
            is_key_pressed : out std_logic;
            is_num_lock_on : out std_logic
        );
    end component;

    component Converter is
        port
        (
            hex_key : in std_logic_vector(7 downto 0);
            is_num_lock_on : in std_logic;
            output : out std_logic_vector(7 downto 0)
        );
    end component;

begin

    ClockGenerator:
    process
    begin
        while not halt_sys_clock loop
            clock <= not clock;
            wait for CLOCK_PERIOD / 2.0;
        end loop;
        wait;
    end process ClockGenerator;

    Stimulus:
    process
        constant NUM_NOISE_SAMPLES: positive := 10;
        constant SWITCH_TIME: time := 2 * DEBOUNCE_PERIOD;
        variable seed1: positive := 1;
        variable seed2: positive := 1;
        variable rrand: real;
        variable nrand: natural;

        -- Performs noisy transition of sig from current value to final value.
        procedure NoisyTransition(signal sig: out std_logic_vector(7 downto 0); final: std_logic_vector(7 downto 0)) is
            constant initial: std_logic_vector(7 downto 0) := sig;
        begin
            for n in 1 to NUM_NOISE_SAMPLES loop
                uniform(seed1, seed2, rrand);
                nrand := natural(round(rrand));
                if nrand = 0 then
                    sig <= initial;
                else
                    sig <= final;
                end if;
                wait for CLOCK_PERIOD / 5.0;
            end loop;
            sig <= final;
            wait for SWITCH_TIME;
        end;

    begin
        reset <= '0';
        input <= "00000000";
        wait for 3 ns;
        reset <= '1';
        wait for CLOCK_PERIOD;
        reset <= '0';

        --
        -- Input
        --

        -- Perform some noisy presses and releases.
        NoisyTransition(input, "00010001");  -- Row 1, Column 1: Num Lock
        NoisyTransition(input, "00000000");

        NoisyTransition(input, "00100010");  -- Row 2, Column 2: 8
        NoisyTransition(input, "00000000");

        NoisyTransition(input, "00110011");  -- Row 3, Column 3: 6
        NoisyTransition(input, "00000000");

        NoisyTransition(input, "01000100");  -- Row 4, Column 4: ENTER
        NoisyTransition(input, "00000000");

        NoisyTransition(input, "00010001");  -- Row 1, Column 1: Num Lock
        NoisyTransition(input, "00000000");

        NoisyTransition(input, "00100010");  -- Row 2, Column 2: 8
        NoisyTransition(input, "00000000");

        NoisyTransition(input, "00110011");  -- Row 3, Column 3: 6
        NoisyTransition(input, "00000000");

        NoisyTransition(input, "01000100");  -- Row 4, Column 4: ENTER
        NoisyTransition(input, "00000000");

        halt_sys_clock <= true;
        wait;
    end process;

    S1:
        Sync
        generic map
        (
            SYNC_BITS => 3,
            BIT_WIDTH => 8   -- 4 bits for rows and 4 bits for columns.
        )
        port map
        (
            clock => clock,
            input => input,
            output => input_sync
        );

    D1:
        Debounce
        generic map
        (
            CLOCK_PERIOD    => CLOCK_PERIOD,
            DEBOUNCE_PERIOD => DEBOUNCE_PERIOD,
            BIT_WIDTH       => 8  -- 4 bits for rows and 4 bits for columns.
        )
        port map
        (
            clock  => clock,
            input  => input_sync,
            output => input_sync_db,
            change => change
        );

    M1:
        Multiplexer
        port map
        (
            reset               => reset,
            clock               => clock,
            key_pressed_strobe  => change,
            columns             => columns,
            rows                => rows,
            binary_key          => key,
            is_key_pressed      => is_key_pressed,
            is_num_lock_on      => is_num_lock_on
        );

    C1:
        Converter
        port map
        (
            hex_key         => key,
            is_num_lock_on  => is_num_lock_on,
            output          => ascii
        );

end architecture;

Simulation of Keypad

Simulation of Keypad Quartus BDF of Keypad

Quartus BDF of Keypad Quartus RTL of Keypad

Quartus RTL of Keypad

\$\endgroup\$
3
\$\begingroup\$

I do not know ANY VHDL, but I think I might be able to help with the working idea.

As I see now, your modules are totally stateless. I'm not sure if they are synchronous or asynchronous, but in this state they could easily be the later.

To implement numlock, you will need some internal state. A simple on-off signal should do it. Pressing numlock should toggle it, and return should be based on it. The later is easier, the former is a bit more different.

I'd suggest creating a new signal - numlock_pressed. It should simply be based on hex_key. You might need to debounce this signal. Then a simple synchronous toggle on the internal state is needed.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.