如何去除VHDL中的冗余进程

How to remove redundant processes in VHDL

不幸的是,我是 VHDL 的新手,但对软件开发并不陌生。 VHDL 中函数的等价性是什么?具体来说,在下面的代码中,我需要去抖动四个按钮而不是一个。显然,重复我的过程代码四次并在我的每个信号后缀上一个数字,使它们在四个实例中是唯一的,这既不专业也不正确。我如何将所有这些压缩到一个进程 "function" 中,我可以向其中 "pass" 信号以便我可以删除所有这些重复代码?

----------------------------------------------------------------------------------
-- Debounced pushbutton examples
----------------------------------------------------------------------------------
library IEEE;

use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity pushbutton is
    generic(
        counter_size : integer := 19           -- counter size (19 bits gives 10.5ms with 50MHz clock)
    );
    port(
        CLK    : in  std_logic;                -- input clock
        BTN    : in  std_logic_vector(0 to 3); -- input buttons
        AN     : out std_logic_vector(0 to 3); -- 7-segment digit anodes ports
        LED    : out std_logic_vector(0 to 3)  -- LEDs
    );
end pushbutton;

architecture pb of pushbutton is
    signal flipflops0   : std_logic_vector(1 downto 0);                               -- input flip flops
    signal flipflops1   : std_logic_vector(1 downto 0);
    signal flipflops2   : std_logic_vector(1 downto 0);
    signal flipflops3   : std_logic_vector(1 downto 0);

    signal counter_set0 : std_logic;                                                  -- sync reset to zero
    signal counter_set1 : std_logic;
    signal counter_set2 : std_logic;
    signal counter_set3 : std_logic;

    signal counter_out0 : std_logic_vector(counter_size downto 0) := (others => '0'); -- counter output
    signal counter_out1 : std_logic_vector(counter_size downto 0) := (others => '0');
    signal counter_out2 : std_logic_vector(counter_size downto 0) := (others => '0');
    signal counter_out3 : std_logic_vector(counter_size downto 0) := (others => '0');

    signal button0      : std_logic;                                                  -- debounce input
    signal button1      : std_logic;
    signal button2      : std_logic;
    signal button3      : std_logic;

    signal result0      : std_logic;                                                  -- debounced signal
    signal result1      : std_logic;
    signal result2      : std_logic;
    signal result3      : std_logic;

begin

    -- Make sure Mercury BaseBoard 7-Seg Display is disabled (anodes are pulled high)
    AN <= (others => '1');

    -- Feed buttons into debouncers
    button0 <= BTN(0);
    button1 <= BTN(1);
    button2 <= BTN(2);
    button3 <= BTN(3);

    -- Start or reset the counter at the right time
    counter_set0 <= flipflops0(0) xor flipflops0(1);
    counter_set1 <= flipflops1(0) xor flipflops1(1);
    counter_set2 <= flipflops2(0) xor flipflops2(1);
    counter_set3 <= flipflops3(0) xor flipflops3(1);

    -- Feed LEDs from the debounce circuitry
    LED(0) <= result0;
    LED(1) <= result1;
    LED(2) <= result2;
    LED(3) <= result3;

    -- Debounce circuit 0
    process (CLK)
    begin
        if (CLK'EVENT and CLK = '1') then
            flipflops0(0) <= button0;
            flipflops0(1) <= flipflops0(0);
            if (counter_set0 = '1') then                  -- reset counter because input is changing
                counter_out0 <= (others => '0');
            elsif (counter_out0(counter_size) = '0') then -- stable input time is not yet met
                counter_out0 <= counter_out0 + 1;
            else                                         -- stable input time is met
                result0 <= flipflops0(1);
            end if;
        end if;
    end process;

    -- Debounce circuit 1
    process (CLK)
    begin
        if (CLK'EVENT and CLK = '1') then
            flipflops1(0) <= button1;
            flipflops1(1) <= flipflops1(0);
            if (counter_set1 = '1') then                  -- reset counter because input is changing
                counter_out1 <= (others => '0');
            elsif (counter_out1(counter_size) = '0') then -- stable input time is not yet met
                counter_out1 <= counter_out1 + 1;
            else                                         -- stable input time is met
                result1 <= flipflops1(1);
            end if;
        end if;
    end process;

    -- Debounce circuit 2
    process (CLK)
    begin
        if (CLK'EVENT and CLK = '1') then
            flipflops2(0) <= button2;
            flipflops2(1) <= flipflops2(0);
            if (counter_set2 = '1') then                  -- reset counter because input is changing
                counter_out2 <= (others => '0');
            elsif (counter_out2(counter_size) = '0') then -- stable input time is not yet met
                counter_out2 <= counter_out2 + 1;
            else                                         -- stable input time is met
                result2 <= flipflops2(1);
            end if;
        end if;
    end process;

    -- Debounce circuit 3
    process (CLK)
    begin
        if (CLK'EVENT and CLK = '1') then
            flipflops3(0) <= button3;
            flipflops3(1) <= flipflops3(0);
            if (counter_set3 = '1') then                  -- reset counter because input is changing
                counter_out3 <= (others => '0');
            elsif (counter_out3(counter_size) = '0') then -- stable input time is not yet met
                counter_out3 <= counter_out3 + 1;
            else                                         -- stable input time is met
                result3 <= flipflops3(1);
            end if;
        end if;
    end process;

end pb;

多位去抖动电路可能如下所示:

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

use     work.Utilities.all;


entity Debouncer is
    generic (
        CLOCK_PERIOD_NS  : positive := 10;
        DEBOUNCE_TIME_MS : positive := 3;

        BITS             : positive
    );
    port (
        Clock  : in  std_logic;

        Input  : in  std_logic_vector(BITS - 1 downto 0);
        Output : out std_logic_vector(BITS - 1 downto 0) := (others => '0')
    );
end entity;

architecture rtl of Debouncer is
begin
    genBits: for i in Input'range generate
        constant DEBOUNCE_COUNTER_MAX  : positive := (DEBOUNCE_TIME_MS * 1000000) / CLOCK_PERIOD_NS;
        constant DEBOUNCE_COUNTER_BITS : positive := log2(DEBOUNCE_COUNTER_MAX);

        signal DebounceCounter         : signed(DEBOUNCE_COUNTER_BITS downto 0) := to_signed(DEBOUNCE_COUNTER_MAX - 3, DEBOUNCE_COUNTER_BITS + 1);

    begin
        process (Clock)
        begin
            if rising_edge(Clock) then
                -- restart counter, whenever Input(i) was unstable within DEBOUNCE_TIME_MS
                if (Input(i) /= Output(i)) then
                    DebounceCounter <= DebounceCounter - 1;
                else
                    DebounceCounter <= to_signed(DEBOUNCE_COUNTER_MAX - 3, DebounceCounter'length);
                end if;

                -- latch input bit, if input was stable for DEBOUNCE_TIME_MS
                if (DebounceCounter(DebounceCounter'high) = '1') then
                    Output(i) <= Input(i);
                end if;
            end if;
        end process;
    end generate;
end architecture;

它希望用户提供频率(以纳秒为单位的周期)和去抖动时间(以毫秒为单位)而不是计数器大小。

引用的包实现了 log2 函数。

VHDL 具有函数,但函数调用是表达式,而不是某些编程语言中的语句或表达式语句。函数调用总是 returns 类型的值和表达式不能表示设计层次结构的一部分。

考虑另一个子程序 class 过程,它们是语句。

去抖动过程和相关声明也可以在不使用过程的情况下进行简化:

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

entity pushbutton is
    generic (
        counter_size:  integer := 19 -- The left bound of debounce counters
    );
    port (
        clk:     in  std_logic;
        btn:     in  std_logic_vector(0 to 3);
        an:      out std_logic_vector(0 to 3);
        led:     out std_logic_vector(0 to 3)
    );
end entity pushbutton;

architecture pb1 of pushbutton is
    -- There are two flip flops for each of four buttons:
    subtype buttons is std_logic_vector(0 to 3);
    type flip_flops is array (0 to 1) of buttons;
    signal flipflops:   flip_flops;
    signal counter_set: std_logic_vector(0 to 3);
    use ieee.numeric_std.all; 
    type counter is array (0 to 3) of
                        unsigned(counter_size downto 0);
    signal counter_out: counter := (others => (others => '0'));
begin
    an <= (others => '1');
    counter_set <= flipflops(0) xor flipflops(1);

DEBOUNCE:
    process (clk)
    begin
        if rising_edge (clk) then
            flipflops(0) <= btn;
            flipflops(1) <= flipflops(0);
            for i in 0 to 3 loop
                if counter_set(i) = '1' then
                    counter_out(i) <=  (others => '0');
                elsif counter_out(i)(counter_size) = '0' then
                    counter_out(i) <= counter_out(i) + 1;
                else
                    led(i) <= flipflops(1)(i);
                end if;
            end loop;
        end if;
    end process;
end architecture pb1;

将部分设计规范移动到程序中:

architecture pb2 of pushbutton is
    -- There are two flip flops for each of four buttons:
    subtype buttons is std_logic_vector(0 to 3);
    type flip_flops is array (0 to 1) of buttons;
    signal flipflops:   flip_flops;
    signal counter_set: std_logic_vector(0 to 3);
    use ieee.numeric_std.all;
    type counter is array (0 to 3) of
                        unsigned(counter_size downto 0);
    signal counter_out: counter := (others => (others => '0'));
    procedure debounce (
    -- Can eliminate formals of mode IN within the scope of their declaration:
        -- signal counter_set:    in  std_logic_vector (0 to 3);
        -- signal flipflops:      in  flip_flops;
        signal counter_out:    inout counter;
        signal led:             out std_logic_vector(0 to 3)
        ) is
    begin
        for i in 0 to 3 loop
            if counter_set(i) = '1' then
                counter_out(i) <=  (others => '0');
            elsif counter_out(i)(counter_size) = '0' then
                counter_out(i) <= counter_out(i) + 1;
            else
                led(i) <= flipflops(1)(i);
            end if;
        end loop;
    end procedure;
begin
    an <= (others => '1');
    counter_set <= flipflops(0) xor flipflops(1);

DEBOUNCER:
    process (clk)

    begin
        if rising_edge (clk) then
            flipflops(0) <= btn;
            flipflops(1) <= flipflops(0);
            -- debounce(counter_set, flipflops, counter_out, led);
            debounce (counter_out, led);
        end if;
    end process;
end architecture pb2;

这里的过程作为顺序语句的集合,不保存任何代码行。

顺序过程调用可用于隐藏重复的混乱。通过合并声明和使用循环语句,已经消除了混乱。设计输入工作量、代码维护工作量和用户可读性之间存在平衡行为,这也会受到编码风格的影响。编码风格也受暗示硬件的 RTL 构造的影响。

将时钟评估移动到过程中需要过程调用是并发语句,类似于您已经拥有的实例化。如果您在体系结构主体中或在使用循环语句时合并声明为块声明项的信号,这似乎不值得。

请注意,resultbutton 声明已被删除。此外,对计数器使用包 numeric_std 和类型 unsigned 可防止无意中分配给具有相同子类型的其他对象。计数器值被视为无符号数,而 counter_set 则不是。

还有一个独立的计数器,用于每个输入的去抖动,就像在原来的那样。在没有独立计数器的情况下,当重复清除单个计数器时,独立输入的一些事件可能会丢失。

此代码尚未通过仿真验证,缺少测试平台。有了实体,两种架构都可以分析和阐述。

除了现在在 for 循环中发现的可以从函数调用中受益的顺序语句之外,这里似乎没有任何其他内容。因为函数调用 returns a 值,所以该值的类型要么需要是复合类型(这里是记录类型),要么被拆分为每个赋值目标的单独函数调用.

还有 generate 语句,它可以将声明和并发语句(这里是一个过程)的零个或多个副本详细说明为带有块声明项的块语句。仅在详细块中使用的任何信号都可以是块声明项。

architecture pb3 of pushbutton is
begin
DEBOUNCERS:
    for i in btn'range generate
        signal flipflops:       std_logic_vector (0 to 1);
        signal counter_set:     std_logic;
        signal counter_out:     unsigned (counter_size downto 0) := 
                                                            (others => '0');
    begin
        counter_set <= flipflops(0) xor flipflops(1);
DEBOUNCE:
        process (clk)
        begin
            if rising_edge (clk) then
                flipflops(0) <= btn(i);
                flipflops(1) <= flipflops(0);
                if counter_set = '1' then
                    counter_out <=  (others => '0');
                elsif counter_out(counter_size) = '0' then
                    counter_out <= counter_out + 1;
                else
                    led(i) <= flipflops(1);
                end if;
            end if;
        end process;
    end generate;
end architecture pb3;

附录

OP 指出在合成架构 pb2 时,由于缺乏模拟和抽象隐藏的复杂性,上述代码中存在错误。虽然去抖动计数器的时间为 10.5 毫秒(50 MHz 时钟),但通用名称 (counter_size) 实际上也是计数器的左边界(作为使用无符号类型的无符号二进制计数器给出)。

上述代码中的错误(同步器中四个按钮各有两个触发器)和简单地遵守 OP 关于计数器的命名约定已得到纠正。

评论中 OP 的综合错误与要求赋值语句左侧或右侧的每个元素都有一个匹配元素有关。

如果不综合代码(OP 所做的),则无法在没有模拟的情况下发现错误。因为找到分配 flipflops(0) 的特定错误唯一必要的是时钟,所以可以编写一个简单的测试平台:

use ieee.std_logic_1164.all;
entity pushbutton_tb is
end entity;

architecture fum of pushbutton_tb is
    signal clk:     std_logic := '0';
    signal btn:     std_logic_vector (0 to 3);
    signal an:      std_logic_vector(0 to 3);
    signal led:     std_logic_vector(0 to 3);
begin
CLOCK:
    process
    begin
        wait for 0.5 ms;
        clk <= not clk;
        if now > 50 ms then
            wait;
        end if;
    end process;

DUT:
    entity work.pushbutton (pb2)
        generic map (
            counter_size =>  4  -- FOR SIMULATION
        )
        port map (
            clk => clk,
            btn => btn,
            an => an,
            led => led
        );

STIMULUS:
    process
    begin
        btn <= (others => '0');
        wait for 20 ms;
        btn(0) <= '1';
        wait for 2 ms;
        btn(1) <= '1';
        wait for 3 ms;
        btn(2) <= '1';
        wait for 6 ms;
        btn(3) <= '1';
        wait;
    end process;
end architecture;

更正后的代码和一个测试平台,以证明在模拟期间分配中没有匹配元素错误。

为两种架构提供了模拟,结果相同。

该泛型用于在测试平台中使用 1 毫秒时钟来减小去抖动计数器的大小(以避免使用不会添加到叙述中的 50 MHz 时钟事件的模拟时间)。

这是第一个架构模拟的输出:

这里的警告是设计应该被模拟。有一个 class 的 VHDL 语义错误条件,仅在运行时(或综合)检查。

添加了抽象以减少 'uniquified' 代码,否则执行相同的代码会引入此类错误。

生成语句在设计层次结构中使用名称不会有这个问题:

generate 语句中发现的并发语句和声明被复制到 generate 语句隐含的任何生成的块语句中。每个块语句代表设计层次结构的一部分。

在设计复杂性和用于调试的波形显示组织之间进行了权衡。

依赖于隐藏重复细节的设计描述无论如何都应该被模拟。这里有两个对所选名称中使用的生成参数 i 的引用,如果忽略参数替换,容易受到与范围相同的错误影响。