The effect you are witnessing is called "bouncing" of the switch.
You need to "debounce" the external input.
How to synchronize an external input
An external input is not synchronous to the internal clock domain. Thus signal edges within the setup or hold time of a register could cause metastability. You need to synchronize your input to the clock domain using a synchronizer. A two-stage synchronizer is usually sufficient.
Example code:
library ieee;
use ieee.std_logic_1164.all;
entity synchronizer is
generic(
nr_of_stages : natural := 2
);
port(
clk : in std_logic;
asynchronous_input : in std_logic;
synchronous_output : out std_logic
);
end entity;
architecture rtl of synchronizer is
signal registers : std_logic_vector(nr_of_stages-1 downto 0);
-- no intialization as this could give a false edge further in the chain.
begin
-- build the registers
register_proc : process(clk)
begin
-- connect the registers end to end
if rising_edge(clk) then
for i in nr_of_stages-1 downto 1 loop
registers(i) <= registers(i-1);
end loop;
registers(0) <= asynchronous_input;
end if;
end process;
-- connect the output to the last register
synchronous_output <= registers(nr_of_stages-1);
end architecture;
Debouncing the signal
Assuming the input is clock synchronous (or synchronized, as described above). You can debounce the signal by ensuring it is stable for a prolonged period. I.e. start a counter when a button is pressed and forward the input when the counter reaches a value.
Example code:
library ieee;
use ieee.std_logic_1164.all;
entity debouncer is
generic(
clock_frequency : positive := 20e6; -- e.g. 20 MHz
settle_time : time := 100 ms
);
port(
clk : in std_logic;
input : in std_logic;
output : out std_logic
);
end entity;
architecture rtl of debouncer is
constant settle_time_in_clocks : positive := integer(real(clock_frequency) * settle_time / 1 sec); -- MHz to ms
signal timer : natural range settle_time_in_clocks-1 downto 0 := settle_time_in_clocks-1;
begin
timer_proc : process(clk)
begin
if rising_edge(clk) then
if input = '0' then
-- not asserted: reset the timer and output
timer <= settle_time_in_clocks-1;
output <= '0';
elsif timer = 0 then
-- timer finished, set the output
output <= '1';
else
-- count down
timer <= timer - 1;
end if;
end if;
end process;
end architecture;
How to count a key press
You detect a key press by detecting a 0-to-1 transition of the input.
Example code:
library ieee;
use ieee.std_logic_1164.all;
entity kpcnt is
port(
clk : in std_logic;
rst : in std_logic;
input_from_debouncer : in std_logic -- assumed to be synchronous to clk
-- some output to be defined
);
end entity;
architecture rtl of kpcnt is
signal input_delay : std_logic;
signal input_rising_edge : std_logic;
use ieee.numeric_std.all;
signal kpcounter : unsigned(7 downto 0) := (others => '0');
begin
-- create delayed input signal
delay_input : process(clk)
begin
if rising_edge(clk) then
input_delay <= input_from_debouncer;
end if;
end process;
-- detect 0->1 transition
input_rising_edge <= '1' when input_from_debouncer = '1' and input_delay = '0' else '0';
-- count the number of 0->1 transitions
kpcounter_proc : process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
kpcounter <= (others => '0');
elsif input_rising_edge = '1' then
kpcounter <= kpcounter + 1;
end if;
end if;
end process;
end architecture;
Links
Here are some links with additional examples:
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…