Math free digital clock

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA project was completed in November 2014.

I set myself the goal of building a basic digital clock, but without using and addition or subtraction. Why? Because I am building a jumbo digital clock (using 8" seven-segment displays from http://www.seeedstudio.com/depot/7-Segment-Display-8-Inches-Red-p-1194.html), and just because it can be done.

All the counting in the project is performed either by "case" statements, Shift Registers or Linear Feedback Shift Registers. No addition is used anywhere in this project!

I can set the time, and it keeps accurate time - what more can one ask for?

Digital clock test.jpg

Good luck to any student who tries to use this a the basis for one of their assignments!

Contents

Source files

Here are all the source files in one zip:

File:Digital clock.zip

clock_top.vhd

This is a purely structural design, that connects all the modules together.

----------------------------------------------------
-- Clock_top.vhd - The Top level for a digital clock
--                 For the Papilio One + LogicStart
--
-- Author :Mike Field (hamster@snap.net.nz)
--
----------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity clock_top is
    Port ( clk32M   : in  STD_LOGIC;
           segments : out STD_LOGIC_VECTOR(6 downto 0);
           btn_hr   : in  STD_LOGIC;
           btn_min  : in  STD_LOGIC;
           anodes   : out STD_LOGIC_VECTOR(3 downto 0));
end clock_top;

architecture Behavioral of clock_top is
	COMPONENT pulse_per_second
	PORT(
		clk : IN std_logic;          
		pps : OUT std_logic
		);
	END COMPONENT;

   COMPONENT adjust
	PORT(
         clk          : IN std_logic;          
         btn_hr       : in  STD_LOGIC;
         btn_min      : in  STD_LOGIC;
         pps          : IN std_logic;
         pps_adjusted : OUT std_logic
		);
   END COMPONENT;
   
	COMPONENT time_keeper
	PORT(
		clk              : IN std_logic;
		pulse_per_second : IN std_logic;          
		sec_units        : OUT std_logic_vector(3 downto 0);
		sec_tens         : OUT std_logic_vector(3 downto 0);
		min_units        : OUT std_logic_vector(3 downto 0);
		min_tens         : OUT std_logic_vector(3 downto 0);
		hr_units         : OUT std_logic_vector(3 downto 0);
		hr_tens          : OUT std_logic_vector(3 downto 0)
		);
	END COMPONENT;

	COMPONENT display
	PORT(
		clk : IN std_logic;
		digit0 : IN std_logic_vector(3 downto 0);
		digit1 : IN std_logic_vector(3 downto 0);
		digit2 : IN std_logic_vector(3 downto 0);
		digit3 : IN std_logic_vector(3 downto 0);          
		segments : OUT std_logic_vector(6 downto 0);
		anodes : OUT std_logic_vector(3 downto 0)
		);
	END COMPONENT;

	COMPONENT synchronise
	PORT(
		clk : IN std_logic;
		raw : IN std_logic;          
		cooked : OUT std_logic
		);
	END COMPONENT;

   signal btn_hr_synced : std_logic;
   signal btn_min_synced : std_logic;

	signal sec_units : std_logic_vector(3 downto 0);
	signal sec_tens  : std_logic_vector(3 downto 0);
	signal min_units : std_logic_vector(3 downto 0);
	signal min_tens  : std_logic_vector(3 downto 0);
	signal hr_units  : std_logic_vector(3 downto 0);
	signal hr_tens   : std_logic_vector(3 downto 0);

	signal pps    : std_logic;
   signal pps_adjusted : std_logic;
begin
	
Inst_pulse_per_second: pulse_per_second PORT MAP(
		clk => clk32M,
		pps => pps
	);

synchronise_btn_hr: synchronise PORT MAP(
		clk => clk32M,
		raw => not btn_hr,
		cooked => btn_hr_synced 
	);

synchronise_btn_min: synchronise PORT MAP(
		clk => clk32M,
		raw => not btn_min,
		cooked => btn_min_synced
	);
 
Inst_adjust: adjust PORT MAP(
		clk          => clk32M,
		btn_hr       => btn_hr_synced,
		btn_min      => btn_min_synced,
		pps          => pps,
		pps_adjusted => pps_adjusted
	);

Inst_time_keeper: time_keeper PORT MAP(
		clk              => clk32M,
		pulse_per_second => pps_adjusted,
		sec_units        => sec_units,
		sec_tens         => sec_tens,
		min_units        => min_units,
		min_tens         => min_tens,
		hr_units         => hr_units,
		hr_tens          => hr_tens
	);   
   
Inst_display: display PORT MAP(
		clk      => clk32M,
		digit0   => min_units,
		digit1   => min_tens,
		digit2   => hr_units,
		digit3   => hr_tens,
		segments => segments,
		anodes   => anodes
	);
end Behavioral;

pulse_per_second.vhd

Using an LFSR to generate a pulse once every 32,000,000 clock cycles.

-----------------------------------------------------------------
-- pulse_per_second.vhd - Generate a single pulse every second.
--                        for the counter-free digital clock.
--
-- Author :Mike Field (hamster@snap.net.nz)
--
-----------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity pulse_per_second is
    Port ( clk : in  STD_LOGIC;
           pps : out  STD_LOGIC);
end pulse_per_second;

architecture Behavioral of pulse_per_second is
   signal lfsr : std_logic_vector(24 downto 0) := "0111111111111111111111111";
begin
p: process(clk)
   begin
      if rising_edge(clk) then
         if lfsr  = "1111111111111111111111111" then
            lfsr <= "0111110111010111001000000";
            pps  <= '1';
         else
            lfsr <= (lfsr(0) xor lfsr(3)) & lfsr(lfsr'high downto 1);
            pps  <= '0';
         end if;
      end if;
   end process;
end Behavioral;

synchronise.vhd

A simple two-stage synchroniser to make sure we don't get errors when the buttons are pushed.

-----------------------------------------------------------------
-- synchronise.vhd - Synchronise an external signal to the local
--                   clock domain.
--
-- Author : Mike Field (hamster@snap.net.nz)
--
-----------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity synchronise is
    Port ( clk : in  STD_LOGIC;
           raw : in  STD_LOGIC;
           cooked : out  STD_LOGIC);
end synchronise;

architecture Behavioral of synchronise is
   signal sync1 : std_logic := '0';
   signal sync2 : std_logic := '0';
begin

process(clk)
   begin
      if rising_edge(clk) then
         cooked <= sync2;
         sync2 <= sync1;
         sync1 <= raw;
      end if;
   end process;

end Behavioral;

adjust.vhd

Time adjustments are performed by adding either 60 or 3600 pulses into the pulse per second signal. Once again a LFSR is used to achieve this.

-----------------------------------------------------------------
-- adjust.vhd - Adjust the time on my maht-free digital clock
--              Add seither 60 or 3600 additional pulses to the pulse 
--              per second signal when the adjust buttons are pushed
--          
--              Only allow one adjustment every 500ms or so
--
-- Author : Mike Field (hamster@snap.net.nz)
--
-----------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity adjust is
    Port ( clk : in  STD_LOGIC;
           btn_hr : in  STD_LOGIC;
           btn_min : in  STD_LOGIC;
           pps : in  STD_LOGIC;
           pps_adjusted : out  STD_LOGIC);
end adjust;

architecture Behavioral of adjust is
   signal holdoff : std_logic_vector(23 downto 0) := (others => '1');
   signal pulses  : std_logic_vector(11 downto 0) := (others => '1');
begin

c: process(clk)
   begin
      if rising_edge(clk) then
         if pps = '1' then
            pps_adjusted <= '1';
         else
            if pulses = "111111111111" then
               pps_adjusted <= '0';
            else
               pps_adjusted <= '1';
               pulses <= pulses(10 downto 0) & (pulses(11) xor pulses(10) xor pulses(9) xor pulses(3));
            end if;            
            
            if holdoff = "111111111111111111111111" then 
               if btn_min = '1' then
                  holdoff(0) <= '0';
                  pulses  <= "111110010011";
               elsif btn_hr = '1' then
                  holdoff(0) <= '0';
                  pulses  <= "110011101000";
               end if;
            else
               holdoff <= holdoff(22 downto 0) & (holdoff(23) xor holdoff(22) xor holdoff(20) xor holdoff(19));               
            end if;
         end if;
      end if;
   end process;

end Behavioral;

time_keeper.vhd

The module that keeps track of hours, minutes and seconds, and is controlled by a pulse-per-second signal.

-----------------------------------------------------------------
-- time_keeper.vhd - Keep track of the time in my math-free
--                   digital clock.
--          
-- Author : Mike Field (hamster@snap.net.nz)
--
-----------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity time_keeper is
    Port ( clk : in  STD_LOGIC;
           pulse_per_second : in  STD_LOGIC;
           sec_units : out  STD_LOGIC_VECTOR (3 downto 0);
           sec_tens  : out  STD_LOGIC_VECTOR (3 downto 0);
           min_units : out  STD_LOGIC_VECTOR (3 downto 0);
           min_tens  : out  STD_LOGIC_VECTOR (3 downto 0);
           hr_units  : out  STD_LOGIC_VECTOR (3 downto 0);
           hr_tens   : out  STD_LOGIC_VECTOR (3 downto 0));
end time_keeper;

architecture Behavioral of time_keeper is
   signal s_units : STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
   signal s_tens  : STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
   signal m_units : STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
   signal m_tens  : STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
   signal h_units : STD_LOGIC_VECTOR(3 downto 0) := "0010";
   signal h_tens  : STD_LOGIC_VECTOR(3 downto 0) := "0001";
begin
   sec_units <= std_logic_vector(s_units);
   sec_tens  <= std_logic_vector(s_tens);
   min_units <= std_logic_vector(m_units);
   min_tens  <= std_logic_vector(m_tens);
   hr_units  <= std_logic_vector(h_units);
   hr_tens   <= std_logic_vector(h_tens);

p: process(clk)
   begin
      if rising_edge(clk) then
         if pulse_per_second = '1' then
            -- Update hours, which uses odd maths
            if s_units = "1001" and s_tens = "0101" and m_units = "1001" and m_tens = "0101" then
               case h_tens & h_units is
                  when "11110001" => h_tens <= "1111"; h_units <= "0010";
                  when "11110010" => h_tens <= "1111"; h_units <= "0011";
                  when "11110011" => h_tens <= "1111"; h_units <= "0100";
                  when "11110100" => h_tens <= "1111"; h_units <= "0101";
                  when "11110101" => h_tens <= "1111"; h_units <= "0110";
                  when "11110110" => h_tens <= "1111"; h_units <= "0111";
                  when "11110111" => h_tens <= "1111"; h_units <= "1000";
                  when "11111000" => h_tens <= "1111"; h_units <= "1001";
                  when "11111001" => h_tens <= "0001"; h_units <= "0000";
                  when "00010000" => h_tens <= "0001"; h_units <= "0001";
                  when "00010001" => h_tens <= "0001"; h_units <= "0010";
                  when others     => h_tens <= "1111"; h_units <= "0001";
               end case;
            end if;

            -- Update minutes
            if s_units = "1001" and s_tens = "0101" and m_units = "1001" then
               case m_tens is
                  when "0000" => m_tens <= "0001";
                  when "0001" => m_tens <= "0010";
                  when "0010" => m_tens <= "0011";
                  when "0011" => m_tens <= "0100";
                  when "0100" => m_tens <= "0101";
                  when others => m_tens <= "0000";
               end case;
            end if;

            if s_units = "1001" and s_tens = "0101" then
               case m_units is
                  when "0000" => m_units <= "0001";
                  when "0001" => m_units <= "0010";
                  when "0010" => m_units <= "0011";
                  when "0011" => m_units <= "0100";
                  when "0100" => m_units <= "0101";
                  when "0101" => m_units <= "0110";
                  when "0110" => m_units <= "0111";
                  when "0111" => m_units <= "1000";
                  when "1000" => m_units <= "1001";
                  when others => m_units <= "0000";
               end case;
            end if;


            -- Update seconds
            if s_units = "1001" then
               case s_tens is
                  when "0000" => s_tens <= "0001";
                  when "0001" => s_tens <= "0010";
                  when "0010" => s_tens <= "0011";
                  when "0011" => s_tens <= "0100";
                  when "0100" => s_tens <= "0101";
                  when others => s_tens <= "0000";
               end case;
            end if;

            case s_units is
               when "0000" => s_units <= "0001";
               when "0001" => s_units <= "0010";
               when "0010" => s_units <= "0011";
               when "0011" => s_units <= "0100";
               when "0100" => s_units <= "0101";
               when "0101" => s_units <= "0110";
               when "0110" => s_units <= "0111";
               when "0111" => s_units <= "1000";
               when "1000" => s_units <= "1001";
               when others => s_units <= "0000";
            end case;
         end if;
      end if;
   end process;

end Behavioral;

display.vhd

Display four decimal digits on the Seven Segment displays. Invalid values display as a blank segment, allowing leading zeros to be suppressed.

The display is run at 25% duty cycle (each of the four digits runs at 6.25% duty)

-----------------------------------------------------------------
-- Clock_top.vhd - The Top level for a math-free digital clock
--                 for the Papilio One + LogicStart
--
-- Author :Mike Field (hamster@snap.net.nz)
--
-----------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity display is
    Port ( clk      : in  STD_LOGIC;
           digit0   : in  STD_LOGIC_VECTOR (3 downto 0);
           digit1   : in  STD_LOGIC_VECTOR (3 downto 0);
           digit2   : in  STD_LOGIC_VECTOR (3 downto 0);
           digit3   : in  STD_LOGIC_VECTOR (3 downto 0);
           segments : out STD_LOGIC_VECTOR (6 downto 0);
           anodes   : out STD_LOGIC_VECTOR (3 downto 0));
end display;

architecture Behavioral of display is
   signal lfsr            : STD_LOGIC_VECTOR(14 downto 0) := (others => '1');
   signal anode_strobe_sr : STD_LOGIC_VECTOR(15 downto 0) := x"0001";
   signal active_digit    : STD_LOGIC_VECTOR( 3 downto 0);
begin

ad: process(active_digit)
   begin
      case active_digit is
         when "0000" => segments <= "1000000";    
         when "0001" => segments <= "1111001";
         when "0010" => segments <= "0100100";
         when "0011" => segments <= "0110000";
         when "0100" => segments <= "0011001";
         when "0101" => segments <= "0010010";
         when "0110" => segments <= "0000010";
         when "0111" => segments <= "1111000";
         when "1000" => segments <= "0000000";
         when "1001" => segments <= "0010000";
         when others => segments <= "1111111";
      end case;
   end process;

c: process(clk)
   begin
      if rising_edge(clk) then
         if lfsr = "111111111111111" then
            if anode_strobe_sr(0) = '1' then
               anodes <= "0111"; 
               active_digit <= digit0;
            elsif anode_strobe_sr(4) = '1' then
               anodes <= "1011"; 
               active_digit <= digit1;
            elsif anode_strobe_sr(8) = '1' then
               anodes <= "1101"; 
               active_digit <= digit2;
            elsif anode_strobe_sr(12) = '1' then
               anodes <= "1110";
               active_digit <= digit3;
            else
               anodes <= "1111"; 
               active_digit <= "1000";
            end if;
            anode_strobe_sr <= anode_strobe_sr(14 downto 0) & anode_strobe_sr(15);
         end if;
         
         lfsr <= (lfsr(0) xor lfsr(1)) & lfsr(lfsr'high downto 1);
      end if;
   end process;
end Behavioral;

papilio_one.ucf

These constraints are for the Papilio One + LogicStart megawing.

#-----------------------------------------------------------------
#-- Papilio_one.ucf - Constraints for my math-free
#--                   digital clock.
#--          
#-- Author : Mike Field <hamster@snap.net.nz>
#--
#-----------------------------------------------------------------
NET CLK32M          LOC="P89"  | IOSTANDARD=LVTTL | PERIOD=31.25ns;               # CLK

NET anodes(3)   LOC="P18"  | IOSTANDARD=LVTTL;                                # A0
NET anodes(2)   LOC="P26"  | IOSTANDARD=LVTTL;                                # A2
NET anodes(1)   LOC="P60"  | IOSTANDARD=LVTTL;                                # A8
NET anodes(0)   LOC="P67"  | IOSTANDARD=LVTTL;                                # A11

NET segments(6) LOC="P62"  | IOSTANDARD=LVTTL;                                # A9
NET segments(5) LOC="P35"  | IOSTANDARD=LVTTL;                                # A4
NET segments(4) LOC="P33"  | IOSTANDARD=LVTTL;                                # A3
NET segments(3) LOC="P53"  | IOSTANDARD=LVTTL;                                # A6
NET segments(2) LOC="P40"  | IOSTANDARD=LVTTL;                                # A5
NET segments(1) LOC="P65"  | IOSTANDARD=LVTTL;                                # A10
NET segments(0) LOC="P57"  | IOSTANDARD=LVTTL;                                # A7

NET btn_hr  LOC="P36"  | IOSTANDARD=LVTTL;
NET btn_min LOC="P34"  | IOSTANDARD=LVTTL;

Personal tools