VHDL:按钮去抖动(或不去抖动,视情况而定)

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

我已通读其他帖子,但似乎无法解决我的问题。我是 VHDL 的新手,所以我确信这是一个简单的修复。

简而言之,按钮没有去抖动。代码编译和比特流程序。在测试台中,按钮可以正常工作,但输出 LED 不会改变。在板上,按下按钮会使随机 LED 点亮(我想是因为弹跳)。根据原理图,输入通过去抖动器。

谁能找出问题所在?以及任何其他提示和技巧总是值得赞赏:)

谢谢!

EDIT1:添加了 rising_edge(clk)。 另请注意,当我按下任一按钮时,所有 LED 都会亮起。

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;

您忘记了 button_counter.vhd 中的 rising_edge

 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;

所以解决这个问题,也许它会起作用(我没有测试设计,因为这个明显的错误):

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

我不确定,但我认为工具链会为此产生一些警告。所以请检查一下。

并且您的测试台不包含任何时钟生成过程,因此您不会有时钟信号。也许这会让您相信您的设计有效(或者您是否忘记了 post 中的时钟 clk_tb 信号?)。

您的代码更新后还有几个问题:

  1. 时钟未在测试台中生成

  2. 测试台中的刺激(按钮按下)时间不够

  3. 去抖器不会为单个时钟启用

为了便于模拟设计验证您的设计已被修改以允许更慢的时钟(看来您实际上使用的是100 MHz 时钟)。这个想法是为了减少计算要求和显示波形存储。

前两点在测试平台中得到解决:

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;

信号名称的 _tb 后缀已被删除(重复输入很痛苦)。

已选择一个时钟周期,其中反弹周期与时钟周期的比率保证允许丢弃 'bounces'。刺激按钮按下可以扩展,模拟在这里是任意的。

请注意,按钮按下值保证跨越一个或多个时钟间隔。 这些应该容忍通过修改 CLKP 来更改时钟周期。

可以修改去抖动间隔DEBT以反映不同开关或按钮的使用,包括老化严重的薄膜开关。去抖间隔是特定开关或按钮的机械特性的结果。传递这些通用常量允许一定程度的平台独立性。

第三点通过对去抖器的更改解决:

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;

去抖器已被修改以获取用于重置和启用计数器的时钟域按钮值count。输出 btn_clr name 保持不变,仅对一个时钟为真,可用作启用。

CLKPDEBT 一起使用可以在相同的模拟时间下更快地执行模拟。

请注意按钮输入的活动状态是硬编码的。这些将连接到可以指定输入极性的设备引脚。

对 button_counter 的修改将通用常量 CLKPDEBT 传递给去抖动器:

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;

在模拟时,我们现在可以看到 LED 向上和向下计数:

运行 测试台和显示各种波形将允许 'zooming in' 显示两个去抖器中的毛刺处理。

通过设计层次结构传递时钟周期和去抖动间隔的修改并不是严格必要的。它们促进了此处用于设计验证的仿真。 (测试台中显示的激励并未详尽地验证设计)。

通过使用通用默认值(使用 100MHz 时钟),设计在目标平台上实现时很有可能发挥作用。 (在去抖器中选择按钮输入的有效极性以支持原始实现。如果您怀疑按钮在获得增量或减量时弹跳,您可以增加 DEBT 值。)

如果特定综合工具无法处理作为通用常量传递的类型 time 的值,您可以将 CLKPDEBT 的各种声明转换为类型 integer 或者简单地传递最大计数。

问题已得到很好的回答,但我想强调一下同步和去抖动的不同技术。

正在同步

为了同步,可以使用一个简单的缓冲区或链,避免为缓冲区或链中的每个阶段创建单独的 signals/variables。可以使用通用常量来控制链的长度(最小为 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;

去抖动

对于去抖动,滞后(历史或记忆的花哨词)可用于创建一种低通滤波器,该滤波器将对按钮的按下和释放进行去抖动,并检测边缘(正边缘和负边缘)不管信号是高电平有效还是低电平有效。输出将保持其当前状态,直到同步输入在 N 个连续时钟周期内保持相反状态:

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;

按钮计数器

与其他答案大致相同,但我使用了去抖器的 rise 输出来触发计数。我还添加了几个 LED 用于视觉按钮反馈。

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;

测试台

一些异步和嘈杂的输入按钮被同步、去抖动和过滤。改造后的输入信号的上升沿触发计数。

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;

模拟