Skip to main content

BCD counter 0-9999 on seven segment VHDL

Short answer

A 4-digit BCD counter increments on a slow tick (e.g. one second). Digits are multiplexed on a 7-segment display by scanning with a fast clock. The design uses tick and scan dividers, BCD to seven-segment decoding, and active-low segment and digit enables.

Write the code for BCD counter to display 0 to 9999 in seven segment using VHDL.

Design

  • A 4-digit BCD counter (0000 to 9999) is stored as four 4-bit digits. A slow tick (one per second) increments the units digit; each digit rolls from 9 to 0 and carries into the next; 9999 wraps to 0000.
  • A seven-segment display (assumed common anode, so segments and digit enables are active low, as on common FPGA boards) shows the four digits. The digits share the segment lines and are scanned (multiplexed) one at a time at about 1 kHz, so all appear lit.
  • Generics TICK_DIV and SCAN_DIV give the number of clock cycles per count and per digit (default for a 50 MHz clock), and can be made small for simulation.
 clk -> [tick divider] -> [4-digit BCD counter]
                                 | digit(0..3)
                                 v
 clk -> [scan div] -> [sel] -> [4:1 mux] -> [7-seg] -> seg
                          +--> [2-to-4 decoder] ---------> an

VHDL code

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

entity bcd_counter_7seg is
  generic (
    TICK_DIV : positive := 50_000_000;   -- clocks per count step (1 s at 50 MHz)
    SCAN_DIV : positive := 50_000        -- clocks per digit (1 ms at 50 MHz)
  );
  port (
    clk : in  std_logic;
    rst : in  std_logic;                          -- active high
    seg : out std_logic_vector(6 downto 0);       -- g f e d c b a, active low
    an  : out std_logic_vector(3 downto 0)        -- digit enable, active low
  );
end entity bcd_counter_7seg;

architecture rtl of bcd_counter_7seg is
  type bcd_array is array (0 to 3) of unsigned(3 downto 0);
  signal digit    : bcd_array := (others => (others => '0'));   -- 0=units ... 3=thousands
  signal tick_cnt : integer range 0 to TICK_DIV - 1 := 0;
  signal scan_cnt : integer range 0 to SCAN_DIV - 1 := 0;
  signal sel      : unsigned(1 downto 0) := (others => '0');    -- digit being displayed
  signal value    : unsigned(3 downto 0);

  -- BCD to seven segment (active low, bit order g f e d c b a)
  function to_seg(d : unsigned(3 downto 0)) return std_logic_vector is
  begin
    case to_integer(d) is
      when 0 => return "1000000";
      when 1 => return "1111001";
      when 2 => return "0100100";
      when 3 => return "0110000";
      when 4 => return "0011001";
      when 5 => return "0010010";
      when 6 => return "0000010";
      when 7 => return "1111000";
      when 8 => return "0000000";
      when 9 => return "0010000";
      when others => return "1111111";             -- blank
    end case;
  end function;
begin
  -- 1) BCD counter 0000 .. 9999
  count_proc : process (clk, rst)
  begin
    if rst = '1' then
      digit    <= (others => (others => '0'));
      tick_cnt <= 0;
    elsif rising_edge(clk) then
      if tick_cnt = TICK_DIV - 1 then
        tick_cnt <= 0;
        if digit(0) /= 9 then
          digit(0) <= digit(0) + 1;
        else
          digit(0) <= (others => '0');
          if digit(1) /= 9 then
            digit(1) <= digit(1) + 1;
          else
            digit(1) <= (others => '0');
            if digit(2) /= 9 then
              digit(2) <= digit(2) + 1;
            else
              digit(2) <= (others => '0');
              if digit(3) /= 9 then
                digit(3) <= digit(3) + 1;
              else
                digit(3) <= (others => '0');       -- 9999 -> 0000
              end if;
            end if;
          end if;
        end if;
      else
        tick_cnt <= tick_cnt + 1;
      end if;
    end if;
  end process;

  -- 2) refresh: show one digit at a time
  scan_proc : process (clk, rst)
  begin
    if rst = '1' then
      scan_cnt <= 0;
      sel      <= (others => '0');
    elsif rising_edge(clk) then
      if scan_cnt = SCAN_DIV - 1 then
        scan_cnt <= 0;
        sel      <= sel + 1;
      else
        scan_cnt <= scan_cnt + 1;
      end if;
    end if;
  end process;

  value <= digit(to_integer(sel));
  seg   <= to_seg(value);
  an    <= "1110" when sel = "00" else
           "1101" when sel = "01" else
           "1011" when sel = "10" else
           "0111";
end architecture rtl;

The code was analysed with GHDL and simulated with small generics: after 123 count steps the scanned digits were 3 (units), 2 (tens), 1 (hundreds), 0 (thousands), showing 0123.

✓ Answer checked by Claude Opus 5.5 (high effort)

All Embedded System chapter 9 questions →