VHDL counter/timer

24.9k Views Asked by At

I'm a VHDL newbie and I'm struggling with the following idea. I think I still misunderstand the idea of counters and timers in VHDL. I will explain it with a simple blinking LED diode. (BTW I'm learning on Spartan-3E FPGA kit.)
So here is my code (I'm not using reset yet)

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;

The oscillator frequency is 50 MHz so one period is 20 ns. If I want to blink with a LED for 1.2 second (which makes 60 000 000 cycles) I need to create a 26 bit vector. So I created a process which is triggered by clk change.
WHAT I THINK IT SHOULD DO: the first line of code is a logic 1 assignment to led1. So the led1 should light until the counter won't count 60 000 000 cycles. When counter counts 60 000 000 cycles, the led logic state should switch to 0 which means no light. As the maximum value of 26 bit number is 67 108 863, the LED should light for 60 000 000 cycles and be turned off for the remaining 7 108 863 cycles. Isn't that right ?
WHAT IT DOES:My impression is, that it's kinda reversed. The LED is off for the most of the time (67 108 063 cycles) and lights for 7 108 863 cycles. Why is this happening ? I don't really get it.

Additional question: How can I achieve to run this process only once/twice/...? E.g. I want to blink with my LED 3 times and then turn it off. Because as far as I know after reaching maximum 26 bit number, the time vector will start counting from the beginning (from 0). And so on, endlessly.

1

There are 1 best solutions below

0
On

First of all, you should divide your design in several parts, which can be entities or several processes. I'll use processes or some one-liner. You can also extract the presented functions into a separate package.

  • part 1: generate a 0.833 Hz signal from 50 MHz system clock
  • part 2: control whether the led is on or off
  • part 3: count the on cycles and disable the led after 3

Part 1

I would suggest to use the type UNSIGNED for a counter, so the '+' operator is defined for this type. I named it timer_us so it's easy to see that this signal is an unsigned. Additionally, you can use (others => '0') in declarations, too.

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';

The last line describes a ternary operator (like z = a ? x : y in C). So the tick signal is '1' if the timer reaches 60,000,000. But there is a fault: Your counter starts at 0 so 60,000,000 cycles are already reached at max - 1.

Some improvements for a more generic counter template:

  • in most cases it's necessary to reset the counter independently from the main reset -> timer_rst
  • define a constant for the maximal counter value -> TIMER_MAX
  • automatically calculate the counter value depending on the maximal counter value -> log2ceil
  • implement a function to convert boolean expressions to STD_LOGIC -> to_sl
  • because timer is of type unsigned, you can use a direct comparison with an integer value

Here the extended counter code:

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));

If you have fun in coding function, you can also invent a function which converts a time (the period for the 0.833 Hz signal) into a cycle count at a given frequency of 50 MHz ;)

Part 2

A blinking LED is a simple 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;

Or as an one-liner (no need for a process block):

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

Part 3

So now you have all the tools to build counters. You can implement a second counter to count from 0 to 2. if 2 is reached you can set a control signal counter2_ctrl which is then feed back to the blink process to stop this process from toggling.

Here the extended one-liner:

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

To your question

The inverse LED output can be caused by low-active circuits on your test board.