VHDL coding tips and tricks: 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 outputs
signal 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...