VHDL coding tips and tricks: April 2022

Tuesday, April 5, 2022

Extension of Digital Clock Project with 7 segment Decoder

In the last post I shared a Digital Clock VHDL code with you along with its testbench. Even though it was synthesizable, it would have been a bit cumbersome to test it on a FPGA board as it was. Because the timer outputs were of unsigned type, you would need to use LED's or something like that to see the time. But who would want a LED digital clock!?

Many FPGA boards have 7 segment displays in them. And this post is to take advantage of such boards. We will take the initial steps to display the time on 7 segment displays. Of course the codes here would need some additional modifications, based on the type of 7 segment displays available on the board and if they share a common bus etc.. But as I mentioned, this is just an initial step.

Without further ado, let me share the codes with you. I have commented the codes plus have uploaded an YouTube video explaining the code. If its useful to you in some way please like and comment on the video. As this helps me with the future direction this blog/YouTube channel will take.

Digital Clock: digital_clock.vhd

You can get it from my last post: Digital Clock VHDL code

Binary to BCD Converter: bin2bcd.vhd

```--Library declaration
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

--Binary to BCD Converter
entity bin2bcd is
port(	binary_in : in unsigned(5 downto 0);
bcd_out : out unsigned(7 downto 0)	--8 bits(4 bits each for 2 digits)
);
end bin2bcd;

architecture Behavioral of bin2bcd is

--actual value=0.0001100110011001101, so divide by 2^19.
constant one_by_ten : unsigned(15 downto 0) := "1100110011001101";
signal result_div_by_ten : unsigned(21 downto 0);
signal msb_digit : unsigned(3 downto 0);

begin

--divide the input by 10. In VHDL, division by a constant is
--easily done by multiplication by its inverse.
result_div_by_ten <=  binary_in*one_by_ten;
--get the decimal part of the result. Bits 18:0 are fractional part
msb_digit <= '0' & result_div_by_ten(21 downto 19);
bcd_out(7 downto 4) <= msb_digit;	--assign it to MSB part of output
--subtract the product, 10*msb_digit, from the input binary number to get LSB digit.
bcd_out(3 downto 0) <= to_unsigned(to_integer(binary_in) - to_integer(msb_digit)*10, 4);

end Behavioral;```

Top Module: digital_clock_topmodule.vhd

```--Libraries
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

--Top module which instantiates and connect together the 3 components:
--Digital clock, Binary to BCD converter, BCD to 7 segment code converter
entity digital_clock_topmodule is
--frequency of the clock passed as a generic parameter.
generic(
CLOCK_FREQ : integer := 50000000
);
port(
Clock : in std_logic;  --system clock
reset : in std_logic;  --resets the time
inc_secs : in std_logic;  --set a pulse here to increment the seconds by 1.
inc_mins : in std_logic;  --set a pulse here to increment the minutes by 1.
inc_hrs : in std_logic;  --set a pulse here to increment the hours by 1.
secs_7seg1 :out unsigned(6 downto 0);  --seconds LSB digit
secs_7seg10 :out unsigned(6 downto 0);  --seconds MSB digit
mins_7seg1 :out unsigned(6 downto 0);  --minutes LSB digit
mins_7seg10 :out unsigned(6 downto 0);  --minutes MSB digit
hrs_7seg1 :out unsigned(6 downto 0);  --hours LSB digit
hrs_7seg10 :out unsigned(6 downto 0)  --hours MSB digit
);
end digital_clock_topmodule;

architecture Behavioral of digital_clock_topmodule is

--Digital clock component
COMPONENT digital_clock
GENERIC( CLOCK_FREQ : integer := 50000000);
PORT(
Clock : IN  std_logic;
reset : IN  std_logic;
inc_secs : IN  std_logic;
inc_mins : IN  std_logic;
inc_hrs : IN  std_logic;
seconds : OUT  unsigned(5 downto 0);
minutes : OUT  unsigned(5 downto 0);
hours : OUT  unsigned(4 downto 0)
);
END COMPONENT;

--Binary to BCD converter component
COMPONENT bin2bcd is
PORT(
binary_in : in unsigned(5 downto 0);
bcd_out : out unsigned(7 downto 0)
);
END COMPONENT;

--Function to convert a BCD digit into a 7 segment code
--Source: https://vhdlguru.blogspot.com/2010/03/vhdl-code-for-bcd-to-7-segment-display.html
--The function is created by converting the code from the above link.
function bcd2seg7(bcd_in : unsigned(3 downto 0)) return unsigned is
variable segment7 : unsigned(6 downto 0);
begin
case bcd_in is
when "0000"=> segment7 :="0000001";  -- '0'
when "0001"=> segment7 :="1001111";  -- '1'
when "0010"=> segment7 :="0010010";  -- '2'
when "0011"=> segment7 :="0000110";  -- '3'
when "0100"=> segment7 :="1001100";  -- '4'
when "0101"=> segment7 :="0100100";  -- '5'
when "0110"=> segment7 :="0100000";  -- '6'
when "0111"=> segment7 :="0001111";  -- '7'
when "1000"=> segment7 :="0000000";  -- '8'
when "1001"=> segment7 :="0000100";  -- '9'
--nothing is displayed when a number more than 9 is given as input.
when others=> segment7 :="1111111";
end case;
return segment7;
end bcd2seg7;

--Declare internal signals
signal seconds : unsigned(5 downto 0);
signal minutes : unsigned(5 downto 0);
signal hours : unsigned(4 downto 0);
signal bcd_secs,bcd_mins,bcd_hrs : unsigned(7 downto 0);
signal hours_extended : unsigned(5 downto 0);

begin

-- Instantiate the Digital Clock component
uut: digital_clock
GENERIC MAP(CLOCK_FREQ => CLOCK_FREQ)
PORT MAP (
Clock => Clock,
reset => reset,
inc_secs => inc_secs,
inc_mins => inc_mins,
inc_hrs => inc_hrs,
seconds => seconds,
minutes => minutes,
hours => hours
);

--convert binary to BCD for seconds
bin2bcd_secs : bin2bcd
port map(
binary_in => seconds,
bcd_out => bcd_secs
);

--convert binary to BCD for minutes
bin2bcd_mins : bin2bcd
port map(
binary_in => minutes,
bcd_out => bcd_mins
);

hours_extended <= '0' & hours;	--just make it the same size as seconds and minutes.
--convert binary to BCD for hours
bin2bcd_hrs : bin2bcd
port map(
binary_in => hours_extended,
bcd_out => bcd_hrs
);

--Call the bcd2seg7 function to convert each BCD digit into a ```
```  --format which can be used on the 7 segment display
secs_7seg1 <= bcd2seg7(bcd_secs(3 downto 0));
secs_7seg10 <= bcd2seg7(bcd_secs(7 downto 4));
mins_7seg1 <= bcd2seg7(bcd_mins(3 downto 0));
mins_7seg10 <= bcd2seg7(bcd_mins(7 downto 4));
hrs_7seg1 <= bcd2seg7(bcd_hrs(3 downto 0));
hrs_7seg10 <= bcd2seg7(bcd_hrs(7 downto 4));

end Behavioral;```

Testbench for the top module: tb_digitalClock_topModule.vhd

```LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
--the below library is used for finishing the simulation after we are done. ```
```--Otherwise it will run continously.
library std;
use std.env.finish;

ENTITY tb_digitalClock_topModule IS
END tb_digitalClock_topModule;

ARCHITECTURE behavior OF tb_digitalClock_topModule IS

-- Component Declaration for the Unit Under Test (UUT)
COMPONENT digital_clock_topmodule is
--frequency of the clock passed as a generic parameter.
generic(
CLOCK_FREQ : integer := 50000000
);
port(
Clock : in std_logic;  --system clock
reset : in std_logic;  --resets the time
inc_secs : in std_logic;  --set a pulse here to increment the seconds by 1.
inc_mins : in std_logic;  --set a pulse here to increment the minutes by 1.
inc_hrs : in std_logic;  --set a pulse here to increment the hours by 1.
secs_7seg1 :out unsigned(6 downto 0);  --seconds LSB digit
secs_7seg10 :out unsigned(6 downto 0);  --seconds MSB digit
mins_7seg1 :out unsigned(6 downto 0);  --minutes LSB digit
mins_7seg10 :out unsigned(6 downto 0);  --minutes MSB digit
hrs_7seg1 :out unsigned(6 downto 0);  --hours LSB digit
hrs_7seg10 :out unsigned(6 downto 0)  --hours MSB digit
);
end COMPONENT;

--Inputs
signal Clock : std_logic := '0';
signal reset : std_logic := '0';
signal inc_secs : std_logic := '0';
signal inc_mins : std_logic := '0';
signal inc_hrs : std_logic := '0';

--Outputs
signal secs_7seg1,secs_7seg10 : unsigned(6 downto 0);```
`   signal mins_7seg1,mins_7seg10 : unsigned(6 downto 0);`
`   signal hrs_7seg1,hrs_7seg10 : unsigned(6 downto 0);`
```   -- Clock period definitions
constant Clock_period : time := 10 ns;

--Clock frequency in Hz. Use a smaller value for testbench. ```
```  --When testing on board it need to be set as 50 million, 100 million etc.
constant CLOCK_FREQ : integer := 10;

BEGIN

-- Instantiate the Unit Under Test (UUT)
uut: digital_clock_topmodule
GENERIC MAP(CLOCK_FREQ => CLOCK_FREQ)
PORT MAP (
Clock => Clock,
reset => reset,
inc_secs => inc_secs,
inc_mins => inc_mins,
inc_hrs => inc_hrs,
secs_7seg1 => secs_7seg1,
secs_7seg10 => secs_7seg10,
mins_7seg1 => mins_7seg1,
mins_7seg10 => mins_7seg10,
hrs_7seg1 => hrs_7seg1,
hrs_7seg10 => hrs_7seg10
);

-- Clock process definitions
Clock_process :process
begin
Clock <= '0';
wait for Clock_period/2;
Clock <= '1';
wait for Clock_period/2;
end process;

-- Stimulus process
stim_proc: process
begin
reset <= '1';
-- hold reset state for 100 ns.
wait for 100 ns;
reset <= '0';
wait for Clock_period*CLOCK_FREQ*60*60*25;  --run the clock for 25 hours

--increment seconds
inc_secs <= '1';	wait for Clock_period;
inc_secs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing seconds once.

--increment seconds 60 times
inc_secs <= '1';	wait for Clock_period*60;
inc_secs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing seconds.

--increment minutes
inc_mins <= '1';	wait for Clock_period;
inc_mins <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 mins after incrementing minutes once.

--increment minutes 60 times
inc_mins <= '1';	wait for Clock_period*60;
inc_mins <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 mins after incrementing minutes.

--increment hours
inc_hrs <= '1';	wait for Clock_period;
inc_hrs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 hours after incrementing hours once.

--increment hours 25 times
inc_hrs <= '1';	wait for Clock_period*25;
inc_hrs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 hours after incrementing hours.

--apply reset
reset <= '1';

--wait for 100 Clock cycles and then finish the simulation.
--with the current settings it will run around 9 ms of simualtion time.
wait for Clock_period*100;
finish;
end process;

END;
```

Top Level Block Diagrams:

`The code was synthesized in Xilinx ISE 14.7. And the top level block diagram is shown below:`

`The inside view of the top module looks like this:`

Sunday, April 3, 2022

Digital Clock (With ability to Set time) And Testbench in VHDL

More than a decade back I had written a Digital Clock module in this blog, which was when I just started learning VHDL. Obviously it had its own shortcomings and through this post, I wanted to rectify these shortcomings. Plus add the ability to set time.

The codes are shared below. They are commented to help you understand the logic. For a detailed understanding watch the video. Also please make sure to like the video if it was helpful and subscribe to my YouTube channel for more such videos in the future.

Digital Clock:

```--Declare the libraries
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;  --we need this one because we are using "unsigned" data type.

entity digital_clock is
--frequency of the clock passed as a generic parameter.
generic( CLOCK_FREQ : integer := 50000000	);
port(
Clock : in std_logic;  --system clock
reset : in std_logic;  --resets the time
inc_secs : in std_logic;  --set a pulse here to increment the seconds by 1.
inc_mins : in std_logic;  --set a pulse here to increment the minutes by 1.
inc_hrs : in std_logic;  --set a pulse here to increment the hours by 1.
seconds :out unsigned(5 downto 0);  --seconds output
minutes :out unsigned(5 downto 0);  --minutes output
hours :out unsigned(4 downto 0)  --hours output
);
end digital_clock;

architecture Behavioral of digital_clock is
```
`--temperory signals as we cant directly perform arithmetic operations on outputssignal secs, mins, hrs : integer := 0;  `
```--counter used for getting the 1 sec duration from the system Clock.signal counter : integer := 0;

begin

process(Clock, reset)
begin
if(reset = '1') then	--reset the time.
secs <= 0;
mins <= 0;
hrs <= 0;
counter <= 0;
elsif(rising_edge(Clock)) then
--increment the seconds. also increment mins and hours if needed.
if(inc_secs = '1') then
if(secs = 59) then
secs <= 0;
if(mins = 59) then
mins <= 0;
if(hrs = 23) then
hrs <= 0;
else
hrs <= hrs+1;
end if;
else
mins <= mins+1;
end if;
else
secs <= secs + 1;
end if;
--increment the minutes. also increment hours if needed.
elsif(inc_mins = '1') then
if(mins = 59) then
mins <= 0;
if(hrs = 23) then
hrs <= 0;
else
hrs <= hrs+1;
end if;
else
mins <= mins+1;
end if;
--increment the hours.
elsif(inc_hrs = '1') then
if(hrs = 23) then
hrs <= 0;
else
hrs <= hrs+1;
end if;
end if;

--regular operation of the clock
if(counter = CLOCK_FREQ-1) then	--counting CLOCK_FREQ times takes 1 second.
counter <= 0;
--check and change values of secs, mins and hours
if(secs = 59) then
secs <= 0;
if(mins = 59) then
mins <= 0;
if(hrs = 23) then
hrs <= 0;
else
hrs <= hrs+1;
end if;
else
mins <= mins+1;
end if;
else
secs <= secs + 1;
end if;
else
counter <= counter+1;
end if;
end if;

end process;

--The internal integer signals are converted into unsigned format.
--The size of the output unsigned signal is assigned via the 2nd parameter(5 or 6 bits)```
```seconds <= to_unsigned(secs, 6);
minutes <= to_unsigned(mins, 6);
hours <= to_unsigned(hrs, 5);

end Behavioral;```

Testbench:

```LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
--the below library is used for finishing the simulation after we are done. ```
```--Otherwise it will run continuously.
library std;
use std.env.finish;

ENTITY tb_digitalClock IS
END tb_digitalClock;

ARCHITECTURE behavior OF tb_digitalClock IS

-- Component Declaration for the Unit Under Test (UUT)

COMPONENT digital_clock
generic( CLOCK_FREQ : integer := 50000000	);
PORT(
Clock : IN  std_logic;
reset : IN  std_logic;
inc_secs : IN  std_logic;
inc_mins : IN  std_logic;
inc_hrs : IN  std_logic;
seconds : OUT  unsigned(5 downto 0);
minutes : OUT  unsigned(5 downto 0);
hours : OUT  unsigned(4 downto 0)
);
END COMPONENT;

--Inputs
signal Clock : std_logic := '0';
signal reset : std_logic := '0';
signal inc_secs : std_logic := '0';
signal inc_mins : std_logic := '0';
signal inc_hrs : std_logic := '0';

--Outputs
signal seconds : unsigned(5 downto 0);
signal minutes : unsigned(5 downto 0);
signal hours : unsigned(4 downto 0);

-- Clock period definitions
constant Clock_period : time := 10 ns;

--Clock frequency in Hz. Use a smaller value for testbench. ```
```  --When testing on board it need to be set as 50 million, 100 million etc.
constant CLOCK_FREQ : integer := 10;

BEGIN

-- Instantiate the Unit Under Test (UUT)
uut: digital_clock
GENERIC MAP(CLOCK_FREQ => CLOCK_FREQ)
PORT MAP (
Clock => Clock,
reset => reset,
inc_secs => inc_secs,
inc_mins => inc_mins,
inc_hrs => inc_hrs,
seconds => seconds,
minutes => minutes,
hours => hours
);

-- Clock process definitions
Clock_process :process
begin
Clock <= '0';
wait for Clock_period/2;
Clock <= '1';
wait for Clock_period/2;
end process;

-- Stimulus process
stim_proc: process
begin
reset <= '1';
-- hold reset state for 100 ns.
wait for 100 ns;
reset <= '0';
wait for Clock_period*CLOCK_FREQ*60*60*25;  --run the clock for 25 hours

--increment seconds
inc_secs <= '1';	wait for Clock_period;
inc_secs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing seconds once.

--increment seconds 60 times
inc_secs <= '1';	wait for Clock_period*60;
inc_secs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing seconds.

--increment minutes
inc_mins <= '1';	wait for Clock_period;
inc_mins <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing minutes once.
--increment minutes 60 times
inc_mins <= '1';	wait for Clock_period*60;
inc_mins <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing minutes.
--increment hours
inc_hrs <= '1';	wait for Clock_period;
inc_hrs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing hours once.
--increment hours 25 times
inc_hrs <= '1';	wait for Clock_period*25;
inc_hrs <= '0';	```
```    wait for Clock_period*CLOCK_FREQ*5;	--wait for 5 secs after incrementing hours.
--apply reset
reset <= '1';

--wait for 100 Clock cycles and then finish the simulation.
--with the current settings it will run around 9 ms of simualtion time.
wait for Clock_period*100;
finish;
end process;

END;
```

So what next?

As an extension of this project I want to show you how you can convert the time into BCD format and then connect a BCD to 7 segment converter to it. This will be useful for those who want to try this clock on a real FPGA board. Look forward to it in the next post.

So until next time...