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

[亡魂溺海] 提交于 2020-06-28 05:14:32

问题


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.

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;

回答1:


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:

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

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.




回答2:


You forget the rising_edge in your button_counter.vhd.

 process(clk)
    variable count : integer := 0;
    begin
        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 process;

So fix this and maybe it works (I don´t test the design, because of this obvious error):

 process(clk)
    variable count : integer := 0;
 begin
        if(rising_edge(clk)) then
            ...
        end if;
 end process;

I´m not sure, but I think the toolchain will produce some warnings for this. So check it please.

And your Testbench doesn´t contain any clock generation process, so you will not have a clock signal. Maybe this will let you believe that your design works (or did you forget the clock clk_tb signal in your post?).




回答3:


The question has been answered well, but I would like to highlight different techniques for synchronising and debouncing.

Synchronising

For synchronising, a simple buffer or chain can be used which avoids creating separate signals/variables for each stage in the buffer or chain. A generic constant can be used to control the length of the chain (minimum of 2):

signal sync_buffer: std_logic_vector(SYNC_BUFFER_MSB downto 0);  -- N-bit synchronisation buffer.
...
sync_buffer <= sync_buffer(SYNC_BUFFER_MSB - 1 downto 0) & input;

Debouncing

For debouncing, hysteresis (a fancy word for history or memory) can be used to create a kind of low pass filter that will debounce both the press and release of a button, and detect edges (both positive and negative) irrespective of whether the signal is active high or active low. The output will stay in its current state until the synchronised input remains in the opposite state for N consecutive clock cycles:

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 := 125 ms;  -- 1/8th second as a rule of thumb for a tactile button/switch.
        SYNC_BITS      : positive := 3    -- Number of bits in the synchronisation buffer (2 minimum).
    );
    port
    (
        clock : in std_logic;
        input : in std_logic;   -- Asynchronous and noisy input.
        output: out std_logic := '0';  -- Synchronised, debounced and filtered output.
        edge  : out std_logic := '0';  -- Goes high for 1 clock cycle on either edge of synchronised and debounced input.
        rise  : out std_logic := '0';  -- Goes high for 1 clock cycle on the rising edge of synchronised and debounced input.
        fall  : out std_logic := '0'   -- Goes high for 1 clock cycle on the falling edge of synchronised and debounced input.
    );
end entity;

architecture V1 of Debounce is

    constant SYNC_BUFFER_MSB: positive := SYNC_BITS - 1;
    signal sync_buffer: std_logic_vector(SYNC_BUFFER_MSB downto 0) := (others => '0');  -- N-bit synchronisation buffer (2 bits minimum).
    alias sync_input: std_logic is sync_buffer(SYNC_BUFFER_MSB);  -- The synchronised input is the MSB of the synchronisation buffer.

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

begin

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

    process(clock)
        variable edge_internal: std_logic := '0';
        variable rise_internal: std_logic := '0';
        variable fall_internal: std_logic := '0';
    begin
        if rising_edge(clock) then
            -- Synchronise the asynchronous input.
            -- MSB of sync_buffer is the synchronised input.
            sync_buffer <= sync_buffer(SYNC_BUFFER_MSB - 1 downto 0) & input;

            edge <= '0';  -- Goes high for 1 clock cycle on either edge.
            rise <= '0';  -- Goes high for 1 clock cycle on the rising edge.
            fall <= '0';  -- Goes high for 1 clock cycle on the falling edge.

            if counter = MAX_COUNT - 1 then  -- If successfully debounced, notify what happened, and reset the counter.
                output <= sync_input;
                edge <= edge_internal;  -- Goes high for 1 clock cycle on either edge.
                rise <= rise_internal;  -- Goes high for 1 clock cycle on the rising edge.
                fall <= fall_internal;  -- Goes high for 1 clock cycle on the falling edge.
                counter <= 0;
            elsif sync_input /= output then
                counter <= counter + 1;
            else
                counter <= 0;
            end if;
        end if;

        -- Edge detection.
        edge_internal := sync_input xor output;
        rise_internal := sync_input and not output;
        fall_internal := not sync_input and output;
    end process;

end architecture;

Button Counter

Much the same as the other answers, but I've used the rise outputs of the debouncers to trigger the counting. I also added a couple of LEDs for visual button feedback.

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

entity ButtonCounter is
    generic
    (
        CLOCK_PERIOD   : time := 20 ns;
        DEBOUNCE_PERIOD: time := 125 ms
    );
    port
    (
        clock : in std_logic;
        btn_up: in std_logic;
        btn_dn: in std_logic;
        led_up: out std_logic;
        led_dn: out std_logic;
        leds  : out std_logic_vector(15 downto 0)
    );
end entity;

architecture V1 of ButtonCounter is

    signal count_up: std_logic;
    signal count_dn: std_logic;

    component Debounce is
        generic
        (
            CLOCK_PERIOD   : time := 20 ns;
            DEBOUNCE_PERIOD: time := 125 ms
        );
        port
        (
            clock : in std_logic;
            input : in std_logic;
            output: out std_logic;
            rise  : out std_logic
        );
    end component;

begin

    DEBOUNCE_BTN_UP:
    Debounce
    generic map
    (
        CLOCK_PERIOD    => CLOCK_PERIOD,
        DEBOUNCE_PERIOD => DEBOUNCE_PERIOD
    )
    port map
    (
        clock  => clock,
        input  => btn_up,
        output => led_up,
        rise   => count_up  -- Goes high for 1 clock cycle on the rising edge of btn_up.
    );

    DEBOUNCE_BTN_DN:
    Debounce
    generic map
    (
        CLOCK_PERIOD    => CLOCK_PERIOD,
        DEBOUNCE_PERIOD => DEBOUNCE_PERIOD
    )
    port map
    (
        clock  => clock,
        input  => btn_dn,
        output => led_dn,
        rise   => count_dn  -- Goes high for 1 clock cycle on the rising edge of btn_dn.
    );

    process(clock)
        variable counter: natural range 0 to 2 ** leds'length - 1 := 0;  -- Specify the range to reduce number of bits that are synthesised.
    begin
        if rising_edge(clock) then
            if count_up then
                counter := counter + 1;
            elsif count_dn then
                counter := counter - 1;
            end if;
            leds <= std_logic_vector(to_unsigned(counter, leds'length));
        end if;
    end process;

end architecture;

Test Bench

Some asynchronous and noisy input buttons are synchronised, debounced and filtered. The positive edges of the reformed input signals trigger the counting.

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

entity ButtonCounter_TB is
end;

architecture V1 of ButtonCounter_TB is

    constant CLOCK_PERIOD   : time := 50 ns;
    constant DEBOUNCE_PERIOD: time := 200 ns;

    signal halt_sys_clock: boolean := false;

    signal clock: std_logic := '0';
    signal btn_up: std_logic;
    signal btn_dn: std_logic;
    signal leds: std_logic_vector(15 downto 0);

    component ButtonCounter is
        generic
        (
            CLOCK_PERIOD   : time := 10 ns;
            DEBOUNCE_PERIOD: time := 125 ms
        );
        port
        (
            clock : in std_logic;
            btn_up: in std_logic;
            btn_dn: in std_logic;
            leds  : out std_logic_vector(15 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; final: std_logic) is
        begin
            for n in 1 to NUM_NOISE_SAMPLES loop
                uniform(seed1, seed2, rrand);
                nrand := natural(round(rrand));
                if nrand = 0 then
                    sig <= not final;
                else
                    sig <= final;
                end if;
                wait for CLOCK_PERIOD / 5.0;
            end loop;
            sig <= final;
            wait for SWITCH_TIME;
        end;

    begin
        btn_up <= '0';
        btn_dn <= '0';
        wait for 3 ns;

        --
        -- Up Button
        --

        -- Perform 4 noisy presses and releases.
        for n in 1 to 4 loop
            NoisyTransition(btn_up, '1');
            NoisyTransition(btn_up, '0');
        end loop;

        --
        -- Down Button
        --

        -- Perform 1 noisy press and release.
        NoisyTransition(btn_dn, '1');
        NoisyTransition(btn_dn, '0');

        halt_sys_clock <= true;
        wait;
    end process;

    DUT:
    ButtonCounter
    generic map
    (
        CLOCK_PERIOD    => CLOCK_PERIOD,
        DEBOUNCE_PERIOD => DEBOUNCE_PERIOD
    )
    port map
    (
        clock  => clock,
        btn_up => btn_up,
        btn_dn => btn_dn,
        leds   => leds
    );

end architecture;

Simulation



来源:https://stackoverflow.com/questions/61630181/vhdl-button-debouncing-or-not-as-the-case-may-be

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!