Search code examples
vhdlfpgaxilinxvivadodebouncing

VHDL: Button debouncing (or not, as the case may be)


I've read through the other posts but can't seem to fix mine. I'm new to VHDL so I'm sure it's a simple fix.

In short, the button isn't debouncing. The code compiles and the bitstream programs. In the testbench, button presses work, but the output LEDs don't change. On the board, pressing a button makes random LEDs light up (I presume because of bouncing). According to the schematic the inputs are going through the debouncers.

Can anyone identify the issue? And any other hints and tips are always appreciated :)

Thanks!

EDIT1: Added rising_edge(clk). Also note, when I press either button, at the time it's depressed all the LEDs light up.

Schematic

button_counter.vhd

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity button_counter is
    port( clk : in std_logic;
         btnU : in std_logic;
         btnD : in std_logic;
          led : out std_logic_vector (15 downto 0));
end button_counter;

architecture behavioral of button_counter is

    component debouncer is
        port(    clk : in std_logic;
                 btn : in std_logic;
             btn_clr : out std_logic);
    end component;

    signal btnU_clr : std_logic;
    signal btnD_clr : std_logic;

    begin

    debouncer_btnU : debouncer port map (clk => clk, btn => btnU, btn_clr => btnU_clr);
    debouncer_btnD : debouncer port map (clk => clk, btn => btnD, btn_clr => btnD_clr);

    process(clk)
        variable count : integer := 0;
        begin
        if (rising_edge(clk)) then
            if(btnU_clr = '1') then count := count + 1;
            elsif(btnD_clr = '1') then count := count - 1;
            end if;
            led <= std_logic_vector(to_unsigned(count, led'length));
        end if;
    end process;

end behavioral;

Debouncer.vhd

library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;

entity debouncer is
    port(    clk : in std_logic;
             btn : in std_logic;
         btn_clr : out std_logic);
end debouncer;

architecture behavioural of debouncer is

    constant delay : integer := 650000; -- 6.5ms
    signal count : integer := 0;
    signal btn_tmp : std_logic := '0';

    begin

    process(clk)
    begin
        if rising_edge(clk) then
            if (btn /= btn_tmp) then
                btn_tmp <= btn;
                count <= 0;
            elsif (count = delay) then
                btn_clr <= btn_tmp;
            else
                count <= count + 1;
            end if;
        end if;
    end process;

end behavioural;

button_counter_tb.vhd

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity button_counter_tb is
end button_counter_tb;

architecture behavioral of button_counter_tb is

signal clk_tb    : std_logic;
signal btnU_tb   : std_logic;
signal btnD_tb   : std_logic;
signal led_tb    : std_logic_vector (15 downto 0);

component button_counter
port(clk    : in std_logic; 
     btnU   : in std_logic;
     btnD   : in std_logic;
     led    : out std_logic_vector (15 downto 0));
end component;

begin

UUT: button_counter port map (clk => clk_tb, btnU => btnU_tb, btnD => btnD_tb, led => led_tb);

process
begin

btnU_tb <= '0';
btnD_tb <= '0'; 

wait for 100ns;
btnU_tb <= '1';

wait for 100ns;
btnU_tb <= '0';

wait for 100ns;
btnU_tb <= '1';

wait for 100ns;
btnD_tb <= '1';

wait for 100ns;
btnU_tb <= '0';

wait for 100ns;
btnD_tb <= '0';

end process;

end behavioral;

Solution

  • After your code update there are several issues remaining:

    1. The clock isn't being generated in the testbench

    2. The stimuli (button presses) aren't adequately timed in the testbench

    3. The debouncer doesn't produce an enable for a single clock

    To facilitate simulation for design validation your design has been modified to allow a slower clock (it appears you're actually using a 100 MHz clock). The idea is to reduce the computation requirements and display waveform storage.

    The first two points are addressed in the testbench:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    entity button_counter_tb is
    end entity button_counter_tb;
    
    architecture behavioral of button_counter_tb is
        -- NOTE: suffix _tb has been removed, it's annoying to type over and over
        signal clk:   std_logic := '0';  -- ADDED default value '0'
        signal btnU:  std_logic;
        signal btnD:  std_logic;
        signal led:   std_logic_vector (15 downto 0);
    
        component button_counter
            generic (                       -- ADDED generic
                CLKP:   time := 10 ns;
                DEBT:   time := 6.5 ms      -- debounce time supports different 
            );                              -- mechanical buttons/switches
            port (
                clk:    in  std_logic; 
                btnU:   in  std_logic;
                btnD:   in  std_logic;
                led:    out std_logic_vector (15 downto 0)
            );
        end component;
    
        constant CLKP:  time := 12.5 us; -- ADDED  just long enough to show debounce
        constant DEBT:  time := 6.5 ms;  -- ADDED
    begin
    
    CLOCK:  -- ADDED clock process
        process
        begin
            wait for CLKP/2;
            clk <= not clk;
            if now > 2 sec then    -- stop simulation
                wait;
            end if;
        end process;
    
    UUT: 
        button_counter 
            generic map (           -- ADDED generic map
                CLKP => CLKP,
                DEBT => DEBT
            )
            port map (
                clk => clk,
                btnU => btnU,
                btnD => btnD,
                led => led
            );
    
    -- STIMULI:
    --     process
    --     begin
    --         btnU_tb <= '0';
    --         btnD_tb <= '0';
    --         wait for 100 ns;
    --         btnU_tb <= '1';
    --         wait for 100 ns;
    --         btnU_tb <= '0';
    --         wait for 100 ns;
    --         btnU_tb <= '1';
    --         wait for 100 ns;
    --         btnD_tb <= '1';
    --         wait for 100 ns;
    --         btnU_tb <= '0';
    --         wait for 100 ns;
    --         btnD_tb <= '0';
    --         wait;  -- ADDED            -- stops simulation
    --     end process;
    UP_BUTTON:
        process
        begin
            btnU <= '0';
            wait for 2 ms;
            btnU <= '1';   -- first button press
            wait for 0.5 ms;
            btnU <= '0';
            wait for 0.25 ms;
            btnU <= '1';
            wait for 7 ms;
            btnU <= '0';
            wait for 100 us;
            btnU <= '1';
            wait for 20 us;
            btnU <= '0';
            wait for 200 ms;
            btnU <= '1';   -- second button press
            wait for 20 us;
            btnU <= '0';
            wait for 20 us;
            btnU <= '1';
            wait for 6.6 ms;
            btnU <= '0';
            wait for 250 ms;
            btnU <= '1';    -- third button press
            wait for 20 us;
            btnU <= '0';
            wait for 20 us;
            btnU <= '1';
            wait for 6.6 ms;
            btnU <= '0';
            wait for 200 ms;
            btnU <= '1';   -- second button press
            wait for 20 us;
            btnU <= '0';
            wait for 20 us;
            btnU <= '1';
            wait for 6.6 ms;
            btnU <= '0';
            wait for 50 us;
            btnU <= '1';
            wait for 1 ms;
            btnU <= '0';
            wait;
        end process;
    DOWN_BUTTON:
        process
        begin
            btnD <= '0';
            wait for 800 ms;
            btnD <= '1';   -- first button press
            wait for 0.5 ms;
            btnD <= '0';
            wait for 0.25 ms;
            btnD <= '1';
            wait for 0.5 ms;
            btnD <= '0';
            wait for 1 ms;
            btnD <= '1';
            wait for 7 ms;
            btnD <= '0';
            wait for 100 us;
            btnD <= '1';
            wait for 20 us;
            btnD <= '0';
            wait for 200 ms;
            btnD <= '1';   -- second button press
            wait for 20 us;
            btnD <= '0';
            wait for 20 us;
            btnD <= '1';
            wait for 6.6 ms;
            btnD <= '0';
            wait for 250 ms;
            wait;
        end process;
    end architecture behavioral;
    

    The _tb suffix to signal names has been removed (it was painful to type in repetitively).

    A clock period has been picked with a ratio of bounce period to clk period guaranteed to allow dropping 'bounces'. The stimului button presses can be extended as can the simulation which is arbitrary here.

    Note the button press values are guaranteed to span one or more clock intervals. These should tolerate the clock period being changed by modifying CLKP.

    The debounce interval DEBT can be modified to reflect the use of different switches or buttons, including membrane switches with severe aging. The debounce interval is a consequence of mechanical characteristics of the particular switches or buttons. Passing these generic constants allows a degree of platform independence.

    The third point is addressed by changes to the debouncer:

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;
    
    entity debouncer is
        generic (                       -- ADDED GENERICS to speed up simulation
            CLKP:   time := 10 ns;
            DEBT:   time := 6.5 ms
        );
        port (
            clk:        in  std_logic;
            btn:        in  std_logic;
            btn_clr:    out std_logic
        );
    end entity debouncer;
    
    architecture behavioural of debouncer is
        -- constant delay: integer := 650000; -- 6.5ms
        constant DELAY: integer := DEBT/CLKP;
        signal count:   integer := 0;
        signal b_enab:  std_logic := '0';  -- RENAMED, WAS btn_tmp
    
        signal btnd0:   std_logic;      -- ADDED for clock domain crossing
        signal btnd1:   std_logic;      -- DITTO
    
        begin
    
    CLK_DOMAIN_CROSS:    -- ADDED process
        process (clk)
        begin
            if rising_edge(clk) then
                btnd0 <= btn;
                btnd1 <= btnd0;
            end if;
        end process;
    
    DEBOUNCE_COUNTER:    -- ADDED LABEL
        process (clk)
        begin
            if rising_edge(clk) then
            --     if btn /= btn_tmp then           -- REWRITTEN
            --         btn_tmp <= btn;
            --         count <= 0;
            --     elsif count = DELAY then
            --         btn_clr <= btn_tmp;
            --     else
            --         count <= count + 1;
            --     end if;
                btn_clr <= '0';       -- btn_clr for only one clock, used as enable
                if  btnd1 = '0' then  -- test for btn inactive state
                    count <= 0;
                elsif count < DELAY then  -- while btn remains in active state
                    count <= count + 1;
                end if;
                if count = DELAY - 1 then  -- why btn_clr '1' or 1 clock
                    btn_clr <= '1';
                end if;
            end if;
        end process;
    end architecture behavioural;
    

    The debouncer has been modified to get a clock domain button value which is used to reset and enable the counter count. The output btn_clr name has been left intact and is true for only one clock and can be used as an enable.

    CLKP and DEBT are used together to allow faster simulation execution while passing the same simulation time.

    Note the active state of the button input is hard coded. These would be connected to device pins where the input polarity can be specified.

    Modifications to button_counter pass generic constants CLKP and DEBT to the debouncers:

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    entity button_counter is
        generic (
            CLKP:   time := 10 ns;   -- GENERIC CONSTANTS for faster simulation
            DEBT:   time := 6.5 ms   -- supports diffeent switches/buttons
        );
        port (
            clk:    in  std_logic;
            btnU:   in  std_logic;
            btnD:   in  std_logic;
            led:    out std_logic_vector (15 downto 0)
        );
    end entity button_counter;
    
    architecture behavioral of button_counter is
        component debouncer is
            generic (
                CLKP:   time := 10 ns;
                DEBT:   time := 6.5 ms
            );
            port (
                clk:        in  std_logic;
                btn:        in  std_logic;
                btn_clr:    out std_logic
            );
        end component;
    
        signal btnU_clr:  std_logic;
        signal btnD_clr:  std_logic;
    begin
    
    debouncer_btnU:
        debouncer
            generic map (
                CLKP => CLKP,
                DEBT => DEBT
            )
            port map (
                clk => clk,
                btn => btnU,
                btn_clr => btnU_clr
            );
    debouncer_btnD:
        debouncer
        generic map (
            CLKP => CLKP,
            DEBT => DEBT
        )
            port map (
                clk => clk,
                btn => btnD,
                btn_clr => btnD_clr
            );
    
        process (clk)
            variable count:  integer := 0;
            begin
            if rising_edge(clk) then
                if btnU_clr = '1' then 
                    count := count + 1;
                elsif btnD_clr = '1'then
                    count := count - 1;
                end if;
                led <= std_logic_vector(to_unsigned(count, led'length));
            end if;
        end process;
    
    end architecture behavioral;
    

    And when simulated we now see the LEDs count up and down: button_counter_tb

    Running the testbench and displaying the various waveforms would allow 'zooming in' to display glitch handling in the two debouncers.

    zoom in

    The modifications to pass the clock period and debounce interval through the design hierarchy wouldn't be strictly essential. They facilitate simulation which is used as here for design validation. (The stimuli shown in the testbench don't exhaustively verify the design).

    By using the generic defaults (with a 100MHz clock) there's a very good chance the design will function when implemented in a target platform. (The active polarity of button inputs is selected in the debouncer to support the original implementation. if you suspect button bounces while getting increments or decrements you can increase the DEBT value.)

    If a particular synthesis tool can't handle value of type time passed as generic constants you can convert the various declarations of CLKP and DEBT to type integer or simply pass the maximum count.