VHDL计数器/计时器

时间:2014-11-16 02:03:50

标签: count vhdl counter fpga timing

我是VHDL新手,我正在努力解决以下问题。我想我仍然误解了VHDL中计数器和计时器的想法。我将用一个简单的闪烁LED二极管来解释它。 (顺便说一下,我正在学习Spartan-3E FPGA套件。)所以这是我的代码(我还没有使用重置)

entity top_level is
  Port( clk : in STD_LOGIC;
        led1 : out STD_LOGIC);
end top_level;

architecture Behavioral of top_level is
    signal timer : STD_LOGIC_VECTOR(25 downto 0) := "00000000000000000000000000"; 
    signal reset: boolean:= false;

begin 

    process (clk) 
    begin
        led1 <= '1';
        if clk='1' and clk'event then
            if reset <= true then
                timer <= (others => '0');
            end if;
            timer <= timer + 1;
        end if;

        if timer <= "11100100111000011100000000" then
            led1 <= '0';
        end if;
    end process;
end Behavioral;

振荡器频率为50 MHz,因此一个周期为20 ns。如果我想用LED闪烁1.2秒(这使得60 000 000次循环),我需要创建一个26位向量。所以我创建了一个由clk更改触发的过程 我认为应该做什么: 第一行代码是对led1的逻辑1赋值。所以led1应该点亮,直到计数器不会计数6 000 000个周期。当计数器计数60 000 000个周期时,LED逻辑状态应切换为0,这意味着没有光。由于26位数的最大值为67 108 863,LED应点亮60 000 000个周期,并在剩余的7 108 863个周期内关闭。不是吗? 它做了什么:我的印象是,它有点逆转。 LED大部分时间处于关闭状态(67 108 063个周期),并且点亮7 108 863个周期。为什么会这样?我真的不明白。

其他问题:如何只运行一次/两次/ ...?例如。我想用我的LED闪烁3次然后关闭它。因为据我所知,在达到最大26位数后,时间向量将从头开始计数(从0开始)。等等,无休止地。

1 个答案:

答案 0 :(得分:3)

首先,您应该将设计划分为多个部分,可以是实体或多个流程。我将使用流程或一些单行程。您还可以将呈现的函数提取到单独的包中。

  • 第1部分:从50 MHz系统时钟生成0.833 Hz信号
  • 第2部分:控制LED是打开还是关闭
  • 第3部分:计算开启周期并在3
  • 之后禁用LED

第1部分

我建议使用UNSIGNED类型作为计数器,因此为此类型定义了'+'运算符。 我将其命名为timer_us,因此很容易看出此信号是无符号的。 此外,您也可以在声明中使用(others => '0')

signal reset      : STD_LOGIC             := '0'; -- disabled reset; TODO move this signal to port

signal timer_us   : UNSIGNED(25 downto 0) := (others => '0');
signal timer_tick : STD_LOGIC;


process(clk)
begin
  if rising_edge(clk) then
    if (reset = '1') then
      timer_us <= (others => '0');
    else
      timer_us <= timer_us + 1;
    end if;
  end if;
end process;

timer_tick <= '1' when (timer_us = to_unsigned(60 * 1000 * 1000), timer_us'length) else '0';

最后一行描述了一个三元运算符(如C中的z = a ? x : y)。因此,如果计时器达到60,000,000,则滴答信号为“1”。但是有一个错误:你的计数器从0开始,所以max - 1已达到60,000,000个周期。

更通用的计数器模板的一些改进:

  • 在大多数情况下,必须独立于主复位重置计数器 - &gt; timer_rst
  • 定义最大计数器值的常数 - &gt; TIMER_MAX
  • 根据最大计数器值自动计算计数器值 - &gt; log2ceil
  • 实现一个将布尔表达式转换为STD_LOGIC的函数 - &gt; to_sl
  • 因为计时器是无符号类型,您可以使用与整数值的直接比较

这里是扩展的计数器代码:

signal reset        : STD_LOGIC     := '0'; -- disabled reset; TODO move this signal to port

function log2ceil(arg : positive) return natural is
    variable tmp : positive     := 1;
    variable log : natural      := 0;
begin
    if arg = 1 then return 0; end if;
    while arg > tmp loop
        tmp := tmp * 2;
        log := log + 1;
    end loop;
    return log;
end function;

function to_sl(condition : BOOLEAN) return STD_LOGIC is
begin
    if condition then
        return '1';
    else
        return '0';
    end if;
end function;

constant TIMER_MAX  : POSITIVE      := 60 * 1000 * 1000 - 1;
constant TIMER_BITS : POSITIVE      := log2ceil(TIMER_MAX);

signal timer_rst    : STD_LOGIC;
signal timer_us     : UNSIGNED(TIMER_BITS - 1 downto 0) := (others => '0');
signal timer_tick   : STD_LOGIC;


timer_rst  <= reset or timer_tick;

process(clk)
begin
  if rising_edge(clk) then
    if (timer_rst = '1') then
      timer <= (others => '0');
    else
      timer <= timer + 1;
    end if;
  end if;
end process;

timer_tick <= to_sl(timer = TIMER_MAX - 1));

如果您对编码功能感兴趣,您还可以创建一个功能,将时间(0.833 Hz信号的周期)转换为给定频率50 MHz的周期计数;)

第2部分

闪烁的LED是一个简单的T-FF:

signal blink : STD_LOGIC := '0';    -- blink will be mapped to a FF, so initialize it


process(clk)
begin
  if rising_edge(clk) then
    if (timer_tick = '1') then
      blink <= not blink;
    end if;
  end if;
end process;

LED <= blink;

或作为单行(不需要处理块):

blink <= blink xor timer_tick when rising_edge(clk);
LED   <= blink;

第3部分

所以现在你拥有了构建计数器的所有工具。您可以实现第二个计数器从0到2的计数。如果达到2,您可以设置一个控制信号counter2_ctrl,然后反馈到闪烁过程以阻止此过程切换。

这里是扩展的单行:

blink_enable <= not counter2_ctrl;
blink        <= blink xor (timer_tick and blink_enable) when rising_edge(clk);

致你的问题

反向LED输出可能是由测试板上的低有效电路引起的。