Introduction: Digital Clock in VHDL
This is a digital clock project created for our Digital System class final project. This project was created by BINUS University Computer Engineering undergraduate students,
- Richie Cheniago - 2540118391
- Nathaniel Melvin Setiawan - 2540120300
- Javier Nugraha Saonard - 2502002500
- Jeremy Andre Muljono - 2540128184
The digital clock shows the seconds, minutes, and hours in a 24-hour format. It also has 4 buttons to increment the minutes and hours, 2 buttons for each, and a reset switch.
Supplies
The only materials used are the Nexys A7-100T board, a USB cable to connect the board, and a device to code on.
Step 1: Components
To start this project, we need to determine what components are needed to create a digital clock. First of all, we would need a clock for the incrementing seconds, which means we need a 1Hz clock. Then we need a component to control how seconds, minutes, and hours affect each other(e.g. when 60 seconds have passed, 1 minute would be shown), we'll call this component. We also need a component to show the actual time itself on the board's 7 segments, this component would also need to divide its anode so that not only one digit would show up on the board. Lastly, which anode would light up needs to be controlled using a clock, we chose that a 1k Hz clock would be appropriate.
Step 2: Clocks
We'll start with the easiest components, the clocks. The Nexys A7-100T board already has an internal clock of 100MHz, our job is to modify it so that we could get our 1Hz and 1kHz clocks. For the 1Hz clock, use the following code;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity clk_1hz is
Port (
clk : in std_logic ;
clk1 : out std_logic
);
end clk_1hz;
architecture Behavioral of clk_1hz is
signal temp1: std_logic:='0';
signal counter1: INTEGER:=0;
begin
process(clk)
begin
if rising_edge(clk) then
counter1<=counter1+1;
if (counter1 = 49999999) then
temp1<= NOT temp1;
counter1<=0;
end if;
end if;
clk1<=temp1;
end process;
end Behavioral;
This component would output a 1Hz which we will use to control how fast a second is. For the 1kHz clock, use the following code;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity clk_1khz is
Port (clk: in std_logic;
clk2: out std_logic );
end clk_1khz;
architecture Behavioral of clk_1khz is
signal temp2: std_logic :='0';
signal counter2: INTEGER :=0;
begin
process(clk)
begin
if rising_edge (clk) then
counter2<=counter2+1;
if (counter2=49999) then
temp2<= NOT temp2;
counter2<=0;
end if;
end if;
clk2<=temp2;
end process;
end Behavioral;
This 1kHz clock will be used to control how fast each anode turns on. Note that any variable name can be freely changed to your liking, what's important is to pay attention to the numbers on the counters on this part
if (counter=49999999) then
which will determine your output clock.
Step 3: Clock Counter
The next component is the clock counter which is used to determine how the clock and user inputs affect the seconds.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
USE ieee.numeric_std.ALL;
entity clock_counter is
Port (clk1: in std_logic;
reset: in std_logic;
houradder: in std_logic_vector (1 downto 0);
minuteadder: in std_logic_vector (1 downto 0);
digit1,digit2,digit3,digit4,digit5,digit6: out std_logic_vector (3 downto 0)
);
end clock_counter;
These are the ports for this component, clk1 is the 1Hz clock used to control the rate of the digital clock, reset is a switch to reset the whole digital clock, houradder and minuteadder are buttons used to increment the hours and the minutes and digit1 to digit6 are signals that will be sent to the next component.
architecture Behavioral of clock_counter is
signal seconds: INTEGER:=0;
seconds is a counter used to determine what each digit will display.
type int_to_bin is array (0 to 9) of std_logic_vector (3 downto 0);
constant int_bin : int_to_bin :=
("0000","0001","0010","0011","0100","0101","0110","0111","1000","1001");
because we will be sending a signal to another component, we need to change it to binary.
begin
process (clk1, reset, minuteadder, houradder, seconds)
begin
if (reset='1' OR seconds=86400) then
seconds<=0;
elsif rising_edge(clk1) then
seconds<=seconds+1;
IF minuteadder(0) ='1' then
seconds<=seconds+60;
elsif minuteadder(1) ='1' then
seconds<=seconds+600;
elsif houradder(0) ='1' THEN
seconds<=seconds+3600;
elsif houradder(1)='1' then
seconds<=seconds+36000;
end if;
end if;
end process;
digit1<=int_bin (((seconds mod 3600) mod 60) mod 10);
digit2<=int_bin (((seconds mod 3600) mod 60 ) /10);
digit3<=int_bin (((seconds mod 3600) /60) mod 10);
digit4<=int_bin (((seconds mod 3600) /60) /10);
digit5<=int_bin ((seconds /3600) mod 10);
digit6<=int_bin ((seconds /3600) /10);
end Behavioral;
This is the code that will control the digital clock, when there is an event on the 1 Hz clock, the seconds will add by 1, and when there are button inputs, the seconds will also increase depending on which button was pressed. The whole digital clock resets when the reset switch is turned on or when it reaches 86400 seconds(24 hours) where it will do the whole counting all over again.
Each digit is determined by mathematical operations and converted into binary so that the next component can read it.
With all of the code combined, the clock_counter component will have this code,
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
USE ieee.numeric_std.ALL;
entity clock_counter is
Port (clk1: in std_logic;
reset: in std_logic;
houradder: in std_logic_vector (1 downto 0);
minuteadder: in std_logic_vector (1 downto 0);
digit1,digit2,digit3,digit4,digit5,digit6: out std_logic_vector (3 downto 0)
);
end clock_counter;
architecture Behavioral of clock_counter is
signal seconds: INTEGER:=0;
type int_to_bin is array (0 to 9) of std_logic_vector (3 downto 0);
constant int_bin : int_to_bin :=
("0000","0001","0010","0011","0100","0101","0110","0111","1000","1001");
begin
process (clk1, reset, minuteadder, houradder, seconds)
begin
if (reset='1' OR seconds=86400) then
seconds<=0;
elsif rising_edge(clk1) then
seconds<=seconds+1;
IF minuteadder(0) ='1' then
seconds<=seconds+60;
elsif minuteadder(1) ='1' then
seconds<=seconds+600;
elsif houradder(0) ='1' THEN
seconds<=seconds+3600;
elsif houradder(1)='1' then
seconds<=seconds+36000;
end if;
end if;
end process;
digit1<=int_bin (((seconds mod 3600) mod 60) mod 10);
digit2<=int_bin (((seconds mod 3600) mod 60 ) /10);
digit3<=int_bin (((seconds mod 3600) /60) mod 10);
digit4<=int_bin (((seconds mod 3600) /60) /10);
digit5<=int_bin ((seconds /3600) mod 10);
digit6<=int_bin ((seconds /3600) /10);
end Behavioral;
Step 4: Mod 6 Counter
The Nexys A7 100T board has a 7-segment display consisting of 8 separate digits, our clock only has 6 digits and we would like to limit the display as well, that's why the next component is a modulo 6 counter.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity mod6counter is
Port (clk2: in std_logic;
WhichDisplay: out std_logic_vector (2 downto 0) );
end mod6counter;
architecture Behavioral of mod6counter is
signal temp : std_logic_vector(2 downto 0);
begin
process(clk2, temp)
begin
if rising_edge(clk2) then
temp<=temp+1;
if temp="110" then
temp<="000";
end if;
end if;
end process;
WhichDisplay <= temp;
end Behavioral;
The mod6counter's code is simple, it simply adds 1 to the counter, WhichDisplay, when an event occurs on the 1kHz clock we made earlier. And then when the counter reaches "110"(6) it will revert back to 0.
Step 5: Anode Picker
The anode picker component is used to pick which segment of the display needs to be turned on,
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity anode_picker is
Port (WhichDisplay: in std_logic_vector (2 downto 0);
anode: out std_logic_vector (7 downto 0) );
end anode_picker;
architecture Behavioral of anode_picker is
signal temp : std_logic_vector (7 downto 0);
begin
process(WhichDisplay , temp)
begin
if WhichDisplay = "000" then
temp <="01111111";
elsif WhichDisplay ="001" then
temp<= "10111111";
elsif WhichDisplay ="010" then
temp<= "11011111";
elsif WhichDisplay ="011" then
temp<= "11101111";
elsif WhichDisplay ="100" then
temp<= "11110111";
elsif WhichDisplay ="101" then
temp<= "11111011";
end if;
end process;
anode<=temp;
end Behavioral;
Using the counter of the mod6counter component before, it chooses which anode to connect to ground for it to turn on and since the WhichDisplay counter is adding at a rate of 1kHz, it will seem to the human eye that all 6 segments(not 8 because we only have 6 digits) are turning on at the same time.
Step 6: Decoder
The last component is the decoder. We have digit1 to digit6 from the clock_counter component but now we need to convert it into the 7-segment display.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE ieee.numeric_std.ALL;
entity decoder is
Port ( digit1,digit2,digit3,digit4,digit5,digit6: in std_logic_vector (3 downto 0);
WhichDisplay : in std_logic_vector (2 downto 0);
segments: out std_logic_vector (7 downto 0)
);
end decoder;
architecture Behavioral of decoder is
type display is array (0 to 9) of std_logic_vector (7 downto 0);
constant converter : display :=
("11000000","11111001","10100100","10110000","10011001","10010010","10000010","11111000",
"10000000","10010000");
signal temp : std_logic_vector (7 downto 0);
begin
process(WhichDisplay ,temp)
begin
if WhichDisplay = "000" then
temp<= converter(to_integer(unsigned(digit1)));
elsif WhichDisplay = "001" then
temp<= converter(to_integer(unsigned(digit2)));
elsif WhichDisplay = "010" then
temp<= converter(to_integer(unsigned(digit3)));
elsif WhichDisplay = "011" then
temp<= converter(to_integer(unsigned(digit4)));
elsif WhichDisplay = "100" then
temp<= converter(to_integer(unsigned(digit5)));
elsif WhichDisplay = "101" then
temp<= converter(to_integer(unsigned(digit6)));
end if;
end process;
segments<=temp;
end Behavioral;
The decoder works by seeing which digit needs to be displayed on which anode using the WhichDisplay counter. The appropriate digit, which has been turned back into an unsigned integer from binary, will then cross-reference to the array that consists of 8-bit binary that the 7-segment display can read to output the correct digit.
Step 7: Main File
Now that we have all the components, we need to gather it into one file which then will declare those components and call signals from one to the other,
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
USE ieee.numeric_std.ALL;
entity counter is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
houradder : in std_logic_vector (1 downto 0);
minuteadder: in std_logic_vector (1 downto 0) ;
anode : out std_logic_vector(7 downto 0);
segments : out std_logic_vector (7 downto 0)
);
end counter;
architecture counter of counter is
component clk_1hz
port(clk: in std_logic ;
clk1: out std_logic
);
end component;
component clk_1khz
port (clk: in std_logic ;
clk2: out std_logic
);
end component;
component mod6counter
port (clk2: in std_logic;
WhichDisplay: out std_logic_vector (2 downto 0));
end component;
component clock_counter
port(clk1: in std_logic ;
reset: in std_logic ;
houradder: in std_logic_vector (1 downto 0);
minuteadder: in std_logic_vector (1 downto 0);
digit1,digit2,digit3,digit4,digit5,digit6: out std_logic_vector (3 downto 0));
end component;
component anode_picker
port (WhichDisplay: in std_logic_vector (2 downto 0);
anode: out std_logic_vector (7 downto 0));
end component;
component decoder
port ( WhichDisplay: in std_logic_vector (2 downto 0);
digit1,digit2,digit3,digit4,digit5,digit6: in std_logic_vector (3 downto 0);
segments: out std_logic_vector (7 downto 0));
end component;
signal clk1 : std_logic :='0';
signal clk2: std_logic :='0';
signal WhichDisplay: std_logic_vector (2 downto 0);
signal digit1,digit2,digit3,digit4,digit5,digit6: std_logic_vector (3 downto 0);
begin
comp1:clk_1hz PORT MAP(
clk, clk1
);
comp2: clk_1khz PORT MAP(
clk, clk2);
comp3: mod6counter PORT MAP(
clk2, WhichDisplay );
comp4: clock_counter PORT MAP(
clk1, reset, houradder, minuteadder, digit1,digit2,digit3,digit4,digit5,digit6);
comp5: anode_picker PORT MAP(
WhichDisplay , anode);
comp6: decoder PORT MAP(
WhichDisplay ,digit1,digit2,digit3,digit4,digit5,digit6,segments);
end counter;
Step 8: Constraints
Before synthesizing and implementing the code, we need to add constraints, the pins for the buttons can be modified to any switches or buttons that you like.
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {segments[0]}]
set_property PACKAGE_PIN E3 [get_ports clk]
set_property PACKAGE_PIN J15 [get_ports reset]
set_property PACKAGE_PIN H15 [get_ports {segments[7]}]
set_property PACKAGE_PIN L18 [get_ports {segments[6]}]
set_property PACKAGE_PIN T11 [get_ports {segments[5]}]
set_property PACKAGE_PIN P15 [get_ports {segments[4]}]
set_property PACKAGE_PIN K13 [get_ports {segments[3]}]
set_property PACKAGE_PIN K16 [get_ports {segments[2]}]
set_property PACKAGE_PIN R10 [get_ports {segments[1]}]
set_property PACKAGE_PIN T10 [get_ports {segments[0]}]
set_property PACKAGE_PIN J17 [get_ports {anode[7]}]
set_property PACKAGE_PIN J18 [get_ports {anode[6]}]
set_property PACKAGE_PIN T9 [get_ports {anode[5]}]
set_property PACKAGE_PIN J14 [get_ports {anode[4]}]
set_property PACKAGE_PIN P14 [get_ports {anode[3]}]
set_property PACKAGE_PIN T14 [get_ports {anode[2]}]
set_property PACKAGE_PIN K2 [get_ports {anode[1]}]
set_property PACKAGE_PIN U13 [get_ports {anode[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {anode[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {houradder[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {houradder[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {minuteadder[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {minuteadder[0]}]
set_property PACKAGE_PIN M17 [get_ports {houradder[0]}]
set_property PACKAGE_PIN P17 [get_ports {houradder[1]}]
set_property PACKAGE_PIN M18 [get_ports {minuteadder[1]}]
set_property PACKAGE_PIN P18 [get_ports {minuteadder[0]}]
Step 9: Final Product
After adding constraints, you should synthesize, implement and write bitstream so that the code can be implemented into the board.
Here is a video demonstration of the finished product, although it is in Bahasa Indonesia, you can still clearly see the functionality of the digital clock and it's buttons.
Step 10: Conclusion and Further Suggestion
Completing this project taught us much more about making a circuit in VHDL and implementing it in FPGA. Our current product is not as complete as we want it to and we will continue perfecting it.
Upon reviewing our product we have a few suggestions for continuing the project, first of all, we would like to change the button inputs not to follow the 1Hz clock to add the minutes and hours, we have tried finding workarounds but we could not find any solutions currently. Another issue is that the user can manually change the clock to above 24 hours(example: 25:30:21) although it beats the purpose of being a digital clock we would like to find a constraint for that. (The current solution is if a user accidentally goes above 24 hours is simply to reset with the reset switch and configure it again)