Wednesday 30 September 2015

Elbert V2 Multiplexing Seven Segment Displays in VHDL

This post continues on from the previous post on controlling the Seven Segment Display and using counters on the Elbert V2 Development board.  If people haven't seen that post then it can be found here:

Elbert V2 FGPA tutorial on Seven Segments Displays using VHDL

The above post implements a simple up down counter to ten using one seven segment display.  Lets now use all three seven segment displays and count up to the maximum possible - which is 999 seconds.

The problem with doing this is that the separate seven segment displays cannot be controlled directly. The segment connections are common between all three displays and controlled by turning the PNP transistors connected to the anodes on and off.  If we turn the PNP transistors on and off very quickly we can make it look like the displays are showing different numbers at the same time.  This is known as multiplexing and this technique is quite common when driving seven segment displays. The actual technique relies on persistence of vision.  If you need more information on multiplexing or persistence of vision then the links below should help...

Multiplexed displays Wikipedia Entry

Persistence of vision Wikipedia Entry

The VHDL code needs to multiplex all three displays and display the result of a counter.  To make things easier lets explain what is needed using a flow diagram - I like flow diagrams, it makes explaining and coding things so much simpler.  Once the flow diagram is clear the coding becomes easier because each part of the code can be related to a part of the flow diagram.

That is the general idea - now for the coding!  Start a new project in Xilinx WebISE - I called mine all_three_segments_counter, you can use any name you like!


Click Next when ready...Ensure the correct settings are chosen for the Elbert V2 Board:


Click Next to display the project summary page...


Click finish to return to the main project screen in WebISE 14.7

Now we need to add a new a new VHDL module so we can write the code - right click on the hierarchy window and select 'Add Source'


Select VHDL module and give the file a suitable name...


Click Next to continue...


You could if you wanted enter the information about the inputs and outputs here...I normally do but this time I'm going to leave things blank - press Next to display the summary window...


Click finish to return to the main screen...The automatically generated VHDL code will be present with comments:


I like to delete the comments in green as they don't really provide anything useful...

Here comes the code...We start with the Entity statement:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;

entity all_three_segments_module is
port ( 
      clock_in : in std_logic;
      Seven_Segment_Enable : out std_logic_vector(2 downto 0);
      Seven_Segment_Display : out std_logic_vector(7 downto 0) 
     );
end all_three_segments_module;

The above code sets the inputs and outputs for the project - the input is the 12 MHz clock and the outputs are the seven segment display enable pins (the three PNP transistors controlling the displays and the control pins for the displays themselves...

architecture Behavioral of all_three_segments_module is

-- Internal signals for processing
signal refresh_count : integer := 0;
signal refresh_clk : std_logic := '1';

signal second_count : integer := 0;
signal second_clk : std_logic := '1';

signal digit_sel : unsigned(1 downto 0);
signal bcd : integer := 0;
signal Seven_Segment_Display_output : std_logic_vector (7 downto 0) := (others => '0');
signal bcd0, bcd1, bcd2 : integer := 0;

signal unit_count : integer := 0;
signal ten_count : integer := 0;

signal hundred_count : integer := 0;

Next we have all of the internal signals needed for processing the two clocks required along with counters and the signals to select which display to update and the internal signals required to drive the displays separately. Finally we have some counters for keeping track of the actual count itself.

begin

-- Divide down the 12 MHz input clock to for the refresh rate
-- and the one second clock

process(Clock_in)

begin

if(clock_in'event and clock_in='1') then 
   refresh_count <= refresh_count+1;
  second_count <= second_count+1;

if(second_count = 750000) then
second_clk <= not second_clk;
second_count <= 1;
end if;

if(refresh_count = 1200) then
refresh_clk <= not refresh_clk;
refresh_count <= 1;
end if;

end if;

end process;  

The above code is a process to read in the 12 MHz clock from the Elbert V2 development board and divide it down to create to separate clocks.  The If statement checks if the 12 MHz clock has changed state and if it is positive.  At that point two counters are incremented, the first counter sets the rate the displays refresh (every 0.0001 seconds) and the other counter counts in one second increments.

-- If the one second clock is high
-- Display the count on Seven segment
-- LED display and count in seconds
-- update the display with respect to
-- the current count
-- if the count is 999 - start again

process(second_clk)

begin

if(second_clk'event and second_clk='1') then
           bcd0 <= unit_count;
           bcd1 <= ten_count;
   bcd2 <= hundred_count;

   unit_count <= unit_count + 1;  

   if (unit_count = 9) then
               unit_count <= 0; 
       ten_count <= ten_count +1;  
   end if;
 
   if (ten_count = 9) and (unit_count = 9) then           ten_count <= 0;
               hundred_count <= hundred_count + 1;
   end if;

   if (hundred_count = 9) and (ten_count = 9) and (unit_count = 9) then   
       hundred_count <= 0;
   end if;
end if;

end process;

The above code checks if the one second clock has changed state and is high - if this is true it then controls the seven segment displays by controlling the PNP transistors connected to the seven segment displays.  The unit count increments every time a second passes.  This is displayed on the right side seven segment display.  If the unit count has reached the count of 9 then the ten count is incremented and the middle display is updated.  If the ten count has reached 9 then the hundred count is incremented and the left side seven segment display is updated.  If the count reaches 999 then the displays are cleared and the count starts again from zero...

process(refresh_clk) --period of clk is 0.0001 seconds.

begin

if(refresh_clk' event and refresh_clk='1') then
digit_sel <= digit_sel + 1;

end if;

end process;

-- multiplexer to select a BCD digit
   with digit_sel select
        bcd <= bcd0 when "00",
               bcd1 when "01",
               bcd2 when others;

-- activate selected digit's anode
   with digit_sel select
        Seven_Segment_Enable <= "110" when "00",
                                "101" when "01",
                                "011" when others;

   with bcd select

Seven_Segment_Display_output(7 downto 0) <= B"00000010" when 0,
   B"10011110" when 1,
   B"00100100" when 2,
   B"00001100" when 3,
   B"10011000" when 4,
   B"01001000" when 5,
   B"01000000" when 6,
   B"00011110" when 7,
   B"00000000" when 8,
   B"00011000" when 9,
   B"11111111" when others;
 
-- send data to seven segment display.

Seven_Segment_Display(7 downto 0) <= Seven_Segment_Display_output(7 downto 0);  

end Behavioral; 

The above code checks whether the refresh clock has changed state and is positive.  If this is true then the appropriate seven segment display is selected, the value required is passed to the display and converted from an integer value into an inverted binary value to drive the appropriate segments. Then the next display is selected and the process is repeated until all of the displays are updated.  This process happens so fast that it cannot be seen by the human eye so it appears that the displays are always on.  If you change the value of refresh_count to a larger value than 1200 say 240000 then it is possible to see the displays updating.  

Here is the complete code - copy this into the VHDL file and then save it.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;

entity all_three_segments_module is
port ( 
       clock_in : in std_logic;
       Seven_Segment_Enable : out std_logic_vector(2 downto 0);
       Seven_Segment_Display : out std_logic_vector(7 downto 0) 
             );
end all_three_segments_module;

architecture Behavioral of all_three_segments_module is

-- Internal signals for processing
signal refresh_count : integer := 0;
signal refresh_clk : std_logic := '1';

signal second_count : integer := 0;
signal second_clk : std_logic := '1';

signal digit_sel : unsigned(1 downto 0);
signal bcd : integer := 0;
signal Seven_Segment_Display_output : std_logic_vector (7 downto 0) := (others => '0');
signal bcd0, bcd1, bcd2 : integer := 0;

signal unit_count : integer := 0;
signal ten_count : integer := 0;
signal hundred_count : integer := 0;

begin

-- Divide down the 12 MHz input clock to for the refresh rate
-- and the one second clock
-- change the values in the counters to change the refresh rate
-- and the clock update speed.

process(Clock_in)

begin

if(clock_in'event and clock_in='1') then 
  refresh_count <= refresh_count+1;
  second_count <= second_count+1;

if(second_count = 750000) then
second_clk <= not second_clk;
second_count <= 1;
end if;

if(refresh_count = 1200) then
refresh_clk <= not refresh_clk;
refresh_count <= 1;
end if;

end if;

end process; 

-- If the one second clock is high
-- Display the count on Seven segment
-- LED display and count in seconds
-- update the display with respect to
-- the current count
-- if the count is 999 - start again

process(second_clk)

begin

if(second_clk'event and second_clk='1') then
      bcd0 <= unit_count;
      bcd1 <= ten_count;
  bcd2 <= hundred_count;

  unit_count <= unit_count + 1;  

  if (unit_count = 9) then
          unit_count <= 0; 
      ten_count <= ten_count +1;  
  end if;
 
  if (ten_count = 9) and (unit_count = 9) then         
   ten_count <= 0;
          hundred_count <= hundred_count + 1;
  end if;

  if (hundred_count = 9) and (ten_count = 9) and (unit_count = 9) then  
      hundred_count <= 0;
  end if;

end if;

end process;

process(refresh_clk) --period of clk is 0.0001 seconds.

begin

if(refresh_clk' event and refresh_clk='1') then
digit_sel <= digit_sel + 1;

end if;

end process;

-- multiplexer to select a BCD digit
   with digit_sel select
        bcd <= bcd0 when "00",
               bcd1 when "01",
               bcd2 when others;

-- activate selected digit's anode
   with digit_sel select
        Seven_Segment_Enable <= "110" when "00",
                                "101" when "01",
                                "011" when others;

   with bcd select

Seven_Segment_Display_output(7 downto 0) <= B"00000010" when 0,
   B"10011110" when 1,
   B"00100100" when 2,
   B"00001100" when 3,
   B"10011000" when 4,
   B"01001000" when 5,
   B"01000000" when 6,
   B"00011110" when 7,
   B"00000000" when 8,
   B"00011000" when 9,
   B"11111111" when others;
 
-- send data to seven segment display.

Seven_Segment_Display(7 downto 0) <= Seven_Segment_Display_output(7 downto 0);  

end Behavioral; 

Save the file!  Always save your work...Nothing is more annoying than not having the work saved if something goes wrong!

Now it's time to add the implementation constraints file - this tells the compiler which pins are connected to the 12 MHz clock and the seven segment displays and the seven segment enable pins. Right click on the hierarchy window again and add source - select implementation constraints file...



Click Next to display the summary file...



Click finish to return to the main project window to display the editor for the constraints file...

CONFIG VCCAUX = "3.3" ;

# Clock 12 MHz
NET "clock_in" LOC = P129  | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz;
  
###################################
#    Seven Segment Display    #
###################################

NET "Seven_Segment_Display[7]" LOC = P117  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[6]" LOC = P116  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[5]" LOC = P115  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[4]" LOC = P113  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[3]" LOC = P112  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[2]" LOC = P111  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[1]" LOC = P110  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[0]" LOC = P114  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

NET "Seven_Segment_Enable[2]" LOC = P124  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Enable[1]" LOC = P121  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

NET "Seven_Segment_Enable[0]" LOC = P120  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; 

Copy and paste the above code into the new file we just created and save it.  It's the information telling the compiler which pins are connected to the input and output statements in the entity section. 

Save the file!

Next click on the green arrow to implement a top module - compile the code!



This will take a few moments - take a break until it's complete! Ignore any warnings that may appear - I'm still working out what that means!  



Next generate a bitstream file and once that is complete it's time to upload it to the development board...Connect up the development board using a USB cable and load up the configuration program - select the appropriate COM port and file and upload it!



Click Program to upload the data and 'Flash' the FPGA - Exciting times!


Once complete you should see the seven segment displays in operation displaying the current count! Here is a video of my board in operation...



If for some reason the source files for this project are required they are here:

Xilinx Project files

If you wanted to modify the operation of this project to read in the count from something external then the entity statement needs to be modified with an additional statement such as:

read_count_in : in std_logic;

Next we need to read this input and then use it to update the second count.  We would need to modify the process statement for second count by adding the following line:

if (read_count_in = '0') then
    unit_count <= unit_count + 1;

The whole section would look like this...

process(second_clk)

begin

if(second_clk'event and second_clk='1') then
      bcd0 <= unit_count;
      bcd1 <= ten_count;
  bcd2 <= hundred_count;

      if (read_count_in = '0') then
   unit_count <= unit_count + 1;

if (unit_count = 9) then
unit_count <= 0; 
ten_count <= ten_count +1;  
end if;
 
if (ten_count = 9) and (unit_count = 9) then         
ten_count <= 0;
hundred_count <= hundred_count + 1;
end if;

if (hundred_count = 9) and (ten_count = 9) and (unit_count = 9) then  
hundred_count <= 0;
end if;

end if;

end if;

end process;

The new input would have to be added to the implementation constraints file - when testing I used a button but you could use any one of the IO pins...

NET "read_count_in" LOC = P80   | PULLUP  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

The updated code as written will update the seven segment displays with the number of times the push button zero is pressed and held.  If you were to use an external input ensure the voltage applied is at 3.3 Vdc and not 5 Vdc - you could damage the FPGA!!! 

Here is the updated code with inputs if required...

modified code with button input

That's all for now - Langster!

Sunday 20 September 2015

Elbert V2 FGPA tutorial on Seven Segments Displays using VHDL

This post will discuss how to use the Seven Segment LED displays on the Elbert V2 Development Board from Numato Labs.  If you haven't been following the earlier posts then please start at the beginning here:

Elbert V2 VHDL Tutorial


I've discussed seven segment displays before here:

Driving LED Seven Segment Displays

For this tutorial lets make the seven segment display and the FPGA behave as a ten second counter. For extra credit lets use a push button to have the counter count down instead of up...

I'm not sure if the Elbert V2 Seven Segment Displays are common anode or common cathode - The place to find out is the manual and schematic diagram for the Elbert V2 Development board which can be found from The Numato Labs product page for Elbert V2 in the downloads section:

Elbert V2 - Numato Labs

Helpfully on Page 6 of the manual the information is provided:
The Seven Segments are common anode - The S1, S2 and S3 connections are the anodes of the displays and they are connected to the 3.3 Vdc supply rail via PNP transistors.  This information is very useful - It tells us how to we need to write the code to turn each display on and how we send signals to the display to make alphanumeric characters appear.

Lets draw up a flow diagram for our idea - we need a way of setting this out to make it clear for us to write the VHDL code.  We could also write pseudo-code but I'm trying something different.

There are several aspects to this project, we have inputs, processing and outputs.  Lets list of how a ten second counter works:
  1. Start.
  2. Initialise a one second counter.
  3. Seven Segment Display powers on and count is set at 0
  4. Seven Segment Display counts up in second increments.
  5. User presses a button. 
  6. Count is reversed.
  7. User releases button - count resumes counting up.
Now lets draw this process as a flow diagram:



The diagram hopefully is clear to follow and more importantly it helps us write the VHDL code.  We now have a guide for each section...
  • We need to generate a one-second clock - we can do this by dividing down the 12 Mhz input clock on the Elbert V2 Development board.
  • We need a counter - this can be an internal register which increments by one each time a second has passed.
  • We need to read if the button has been pressed and drive the seven segment display accordingly.
Ok - enough design lets start a project and write some Code.  Load up Xilinx WebISE 14.7 and start a new project.  I called mine Up_Down_Counter...


Click Next to continue...Make sure all the setting shown below are applied!


Click Next to display the summary screen...


Click finish to return to the main project screen...

Right click on the hierarchy box and add a new source...


Choose to add a VHDL module - I called mine up_down_counter_module...


Click next to continue...


For our project we need to read in the 12 MHz clock, read in a button and output the values to the seven segment display - make sure all of the above settings are chosen before clicking Next....


Click finish to return to the main screen - The automatically generated code will be displayed:


I like to delete the comments as I don't think they help - you should comment code as you write it in my opinion.  

The software has automatically generated the entity statement of our project for us.  The entity statement defines how the physical inputs and outputs interact with the FPGA device.  In this case we have a 12 MHz clock signal input, a push button input and outputs to enable and drive the seven segment displays - we are using logic vectors to control the seven segment displays.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- define inputs and outputs
-- of the up down counter

entity up_down_counter_module is
    Port ( 
       clock_in : in  STD_LOGIC;
           reverse_button : in  STD_LOGIC;
           Seven_Segment_Enable : in  STD_LOGIC_VECTOR (2 downto 0);
           Seven_Segment_Display : in  STD_LOGIC_VECTOR (7 downto 0)
);
end up_down_counter_module;

Anyway now it's the hard part the code writing...According to the flow diagram the first job is to write the code which generates a one second timer.  How do we make a one-second timer in VHDL? There are several methods but for this example we are going to take the 12 MHz input clock and divide it down.  In the first part of the architecture statement we need to write code which reads in the 12 MHz clock and divides it down to 1 second.  Every time a second has passed we need to increment a counter.  Here is the code to achieve this:

architecture Behavioral of up_down_counter_module is

-- Internal signals for processing

signal bcd   : integer := 0;
signal count : integer := 0;
signal clk : std_logic :='1';

begin

-- enable Seven Segment Display
Seven_Segment_Enable (2 downto 0) <="110"; 

-- Divide down the 12 MHz input clock to 1 Hz

process(Clock_in)
begin

if(Clock_in'event and Clock_in='1') then 
count <=count+1;

if(count = 6000000) then
clk <= not clk;
count <= 1;
end if;

end if;

end process;

Let's explain the above VHDL code:

The first line is the architecture statement for VHDL.

architecture Behavioral of stop_watch is

The next lines create two internal signal variables to store the count and generate a one second clock.  There is also a variable to convert an integer value into Binary Coded Decimal.

-- Internal signals for processing

signal count : integer := 0;
signal clk : std_logic :='1';
signal bcd   : integer := 0;

Then we have the 'begin' statement:

-- enable Seven Segment Display
Seven_Segment_Enable (2 downto 0) <="110"; 

We then enable one of the seven segment displays - if we wanted to enable them all then we would have:

Seven_Segment_Enable (2 downto 0) <="000";

The displays are active low - that's because the drive transistors are PNP and work when the signal applied to the base pin is lower than that on the emitter pin.  We can also address a single display only by writing the code:

Seven_Segment_Enable (1) <="0";

After that we have a process statement which is similar to a function in a high-level programming language:

process(Clock_in)
begin
if(Clock_in'event and Clock_in='1') then 
count <=count+1;
if(count = 6000000) then
clk <= not clk;
count <= 1;
end if;
end if;

end process;

The code inside the process statement itself:

if(Clock_in'event and Clock_in='1') then 
count <=count+1;

If the 12 MHz clock has changed state and the clock is positive then increment the count variable by 1.

if(count = 6000000) then
clk <= not clk;
count <= 1;
end if;

If the count has reached six million then change the state of the clk variable and then reset the count to 1.  We do this because:

12 MHz = 12,000,000 Hz

12,000,000 Hz = 8.3333333333333333333333333333333 * 10^-8 seconds as 1 / f (Hz) = time (seconds)

8.3333333333333333333333333333333 * 10^-8 seconds * 6000000 = 0.5 seconds

Therefore clk will change state every 0.5 seconds making a one second clock period.  If you wanted a faster or slower clock all that is required is to calculate the speed required and change the integer value in the if statement to that value.  Any value greater than 6000000 will be slower than a second. Any value less than 6000000 will be faster than a second... 

end if;

end process;

The next lines (shown above) end the if statement and the process.

So that is the 1 second clock sorted and the display is initialised...Now we need to count when a second has passed and display the count. We then need to check if the button has been pressed.  If the button is pressed and held we need to count down from 9 but if the button hasn't been pressed and held then we count up from 0 and then we send the data to the seven segment display - Easy hmm!

-- If the 1 Hz clock is high
-- and the button has not been
-- pressed and held then count up.
-- Display the count on Seven segment
-- LED display

-- if the button has been pressed
-- and held count down.
-- Display the count on Seven segment
-- LED display

process(clk) --period of clk is 1 second.

variable Seven_Segment_Display_output : std_logic_vector (7 downto 0) := (others => '0');

begin

    if(clk' event and clk='1') then

if (reverse_button = '1') then
    bcd <= bcd + 1;  
 
    if (bcd = 9) then
        bcd <= 0;
    end if;

case bcd is
 
  when 0      => Seven_Segment_Display_output(7 downto 0) := B"00000010";
  when 1      => Seven_Segment_Display_output(7 downto 0) := B"10011110";
  when 2      => Seven_Segment_Display_output(7 downto 0) := B"00100100";
  when 3      => Seven_Segment_Display_output(7 downto 0) := B"00001100";
  when 4      => Seven_Segment_Display_output(7 downto 0) := B"10011000";
  when 5      => Seven_Segment_Display_output(7 downto 0) := B"01001000";
  when 6      => Seven_Segment_Display_output(7 downto 0) := B"01000000";
  when 7      => Seven_Segment_Display_output(7 downto 0) := B"00011110";
  when 8      => Seven_Segment_Display_output(7 downto 0) := B"00000000";
  when 9      => Seven_Segment_Display_output(7 downto 0) := B"00011000";
  when others => Seven_Segment_Display_output(7 downto 0) := B"11111111";
 
end case;  

else if (reverse_button = '0') then
   
     bcd <= bcd - 1;  
 
     if (bcd = 0) then
 bcd <= 9;
     end if;

case bcd is
 
   when 0      => Seven_Segment_Display_output(7 downto 0) := B"00000010";
   when 1      => Seven_Segment_Display_output(7 downto 0) := B"10011110";
   when 2      => Seven_Segment_Display_output(7 downto 0) := B"00100100";
   when 3      => Seven_Segment_Display_output(7 downto 0) := B"00001100";
   when 4      => Seven_Segment_Display_output(7 downto 0) := B"10011000";
   when 5      => Seven_Segment_Display_output(7 downto 0) := B"01001000";
   when 6      => Seven_Segment_Display_output(7 downto 0) := B"01000000";
   when 7      => Seven_Segment_Display_output(7 downto 0) := B"00011110";
   when 8      => Seven_Segment_Display_output(7 downto 0) := B"00000000";
   when 9      => Seven_Segment_Display_output(7 downto 0) := B"00011000";
   when others => Seven_Segment_Display_output(7 downto 0) := B"11111111";
 
end case;  

end if;

end if;

end if;

-- send data to seven segment display.

Seven_Segment_Display(7 downto 0) <= Seven_Segment_Display_output(7 downto 0);  

end process;


end Behavioral;

That's quite a lot of code!  Lets again explain it:

process(clk) --period of clk is 1 second.

variable Seven_Segment_Display_output : std_logic_vector (7 downto 0) := (others => '0');

The first line starts a process statement which uses the current 1 Hz clock (clk) value.  The line after that is a variable - we need a variable to convert the integer count data into eight bits for driving the seven segment display.  

begin

    if(clk' event and clk='1') then

if (reverse_button = '1') then
    bcd <= bcd + 1;   
 
    if (bcd = 9) then
        bcd <= 0;
    end if;

The above code snippet begins the process checking the status of the 1 Hz clock (clk).  If the clock has changed state and is positive then the state of the reverse button is checked.  If the button is high (not pressed and held) then the bcd integer signal is incremented.  If the value of bcd is 9 then it is cleared and set to 0.   

        case bcd is
  
  when 0      => Seven_Segment_Display_output(7 downto 0) := B"00000010";
  when 1      => Seven_Segment_Display_output(7 downto 0) := B"10011110";
  when 2      => Seven_Segment_Display_output(7 downto 0) := B"00100100";
  when 3      => Seven_Segment_Display_output(7 downto 0) := B"00001100";
  when 4      => Seven_Segment_Display_output(7 downto 0) := B"10011000";
  when 5      => Seven_Segment_Display_output(7 downto 0) := B"01001000";
  when 6      => Seven_Segment_Display_output(7 downto 0) := B"01000000";
  when 7      => Seven_Segment_Display_output(7 downto 0) := B"00011110";
  when 8      => Seven_Segment_Display_output(7 downto 0) := B"00000000";
  when 9      => Seven_Segment_Display_output(7 downto 0) := B"00011000";
  when others => Seven_Segment_Display_output(7 downto 0) := B"11111111";
  
end case;

Next we have a case statement - this is similar to a switch statement in a high level programming language like C.  What this code does is convert the integer value of bcd into the bit value needed to drive the seven segment display.   

Seven segment displays are 7 LEDS arranged in a pattern from to create an alphanumeric character. Each LED has a letter assigned to it to help us work out which LEDS to turn on to create the character.
In order to create the numbers from 0 to 9 we have to turn the LEDS on in the correct sequence.  For example to make up the number zero with the decimal point - we need to turn on the segments: 

A, B, C, D, E, F, not G, DP - this can be represented as bits like this - 1, 1, 1, 1, 1, 1, 0, 1, 

And bit can be represented as a binary number = 11111101(b) which is written in VHDL as:

B"11111101";

Because the seven segment display on the Elbert V2 Development board is active low we have to invert the bits to make the segments light up:

B"00000010";

If we wanted to remove the decimal point we would change the number to:

B"00000011";

It is also possible to display the characters A to F for Hexadecimal numbers, just turn on the required segments.  Here are the binary numbers for all of the characters we may need to display - I've inverted them ready for use with the Elbert V2 Development Board:

B"00000010" = 0
B"10011110" = 1
B"00100100" = 2
B"00001100" = 3
B"10011000" = 4
B"01001000" = 5
B"01000000" = 6
B"00011110" = 7
B"00000000" = 8
B"00011000" = 9

B"00010000" = A
B"11000000" = b
B"01100010" = C
B"01000010" = d
B"00001100" = E
B"00011100" = F

That's it for the counting up code - the counting down code is very similar but in reverse.  If the button is held down then the value of bcd is subtracted until it reaches zero and is reset.

Here is all of the code together just in case it's needed:


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- define inputs and outputs
-- of the up down counter

entity up_down_counter_module is
    Port ( 
        clock_in : in  STD_LOGIC;
           reverse_button : in  STD_LOGIC;
           Seven_Segment_Enable : in  STD_LOGIC_VECTOR (2 downto 0);
           Seven_Segment_Display : in  STD_LOGIC_VECTOR (7 downto 0)
 );
end up_down_counter_module;

architecture Behavioral of up_down_counter_module is

-- Internal signals for processing

signal bcd   : integer := 0;
signal count : integer := 0;
signal clk : std_logic :='1';

begin

-- enable Seven Segment Display
Seven_Segment_Enable (2 downto 0) <="110"; 

-- Divide down the 12 MHz input clock to 1 Hz

process(Clock_in)
begin

if(Clock_in'event and Clock_in='1') then 
count <=count+1;

if(count = 6000000) then
clk <= not clk;
count <= 1;
end if;

end if;

end process;

-- If the 1 Hz clock is high
-- and the button has not been
-- pressed and held then count up.
-- Display the count on Seven segment
-- LED display

-- if the button has been pressed
-- and held count down.
-- Display the count on Seven segment
-- LED display

process(clk) --period of clk is 1 second.

variable Seven_Segment_Display_output : std_logic_vector (7 downto 0) := (others => '0');

begin

    if(clk' event and clk='1') then

if (reverse_button = '1') then
    bcd <= bcd + 1;   
 
    if (bcd = 9) then
        bcd <= 0;
    end if;

case bcd is
  
  when 0      => Seven_Segment_Display_output(7 downto 0) := B"00000010";
  when 1      => Seven_Segment_Display_output(7 downto 0) := B"10011110";
  when 2      => Seven_Segment_Display_output(7 downto 0) := B"00100100";
  when 3      => Seven_Segment_Display_output(7 downto 0) := B"00001100";
  when 4      => Seven_Segment_Display_output(7 downto 0) := B"10011000";
  when 5      => Seven_Segment_Display_output(7 downto 0) := B"01001000";
  when 6      => Seven_Segment_Display_output(7 downto 0) := B"01000000";
  when 7      => Seven_Segment_Display_output(7 downto 0) := B"00011110";
  when 8      => Seven_Segment_Display_output(7 downto 0) := B"00000000";
  when 9      => Seven_Segment_Display_output(7 downto 0) := B"00011000";
  when others => Seven_Segment_Display_output(7 downto 0) := B"11111111";
  
end case;  

else if (reverse_button = '0') then
    
     bcd <= bcd - 1;   
 
     if (bcd = 0) then
 bcd <= 9;
     end if;

case bcd is
  
   when 0      => Seven_Segment_Display_output(7 downto 0) := B"00000010";
   when 1      => Seven_Segment_Display_output(7 downto 0) := B"10011110";
   when 2      => Seven_Segment_Display_output(7 downto 0) := B"00100100";
   when 3      => Seven_Segment_Display_output(7 downto 0) := B"00001100";
   when 4      => Seven_Segment_Display_output(7 downto 0) := B"10011000";
   when 5      => Seven_Segment_Display_output(7 downto 0) := B"01001000";
   when 6      => Seven_Segment_Display_output(7 downto 0) := B"01000000";
   when 7      => Seven_Segment_Display_output(7 downto 0) := B"00011110";
   when 8      => Seven_Segment_Display_output(7 downto 0) := B"00000000";
   when 9      => Seven_Segment_Display_output(7 downto 0) := B"00011000";
   when others => Seven_Segment_Display_output(7 downto 0) := B"11111111";
  
end case;  

end if;

end if;

end if;

-- send data to seven segment display.

Seven_Segment_Display(7 downto 0) <= Seven_Segment_Display_output(7 downto 0);  

end process;


end Behavioral;

Now we need to generate an implementation constraints file - this is the file that tells the compiler which pins we want to use on the FPGA device.  As we are using the Elbert V2 development board we can use the schematic diagram to tell us which pins connect to what:
  • Push button zero is connected to pin P80 - lets use this for the reverse_button.
  • The seven segment display enable pins are connected to P120, P121 and P124 - lets use this for the seven segment enable logic vector
  • The seven segment drive pins are connected P117, P116, P115, P113, P112, P111, P110 and P114. 
  • The 12 MHz clock is connected to pin P129.
From this information we can write the implementation constraints file.  Right click on the hierarchy window and select add new source this time add an implementation constraints file:



Click next to display the summary:



Click finish to return to the main project screen - copy and paste the code below into the newly open file:

CONFIG VCCAUX = "3.3" ;

# Clock 12 MHz
NET "clock_in" LOC = P129  | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz;
  
###################################
#        Seven Segment Display    #
###################################

NET "Seven_Segment_Display[7]" LOC = P117  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[6]" LOC = P116  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[5]" LOC = P115  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[4]" LOC = P113  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[3]" LOC = P112  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[2]" LOC = P111  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[1]" LOC = P110  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Display[0]" LOC = P114  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

NET "Seven_Segment_Enable[2]" LOC = P124  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Enable[1]" LOC = P121  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
NET "Seven_Segment_Enable[0]" LOC = P120  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; 

###################################
#  Switches                       # 
###################################


NET "reverse_button" LOC = P80   | PULLUP  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;

Save the file and click on the green arrow to compile the code and "Implement Top Module"



The code will now be compiled and the FPGA internal signals will be routed or implemented.  There should not be any warnings or errors. Once complete you can create a bitstream file:



Now right click on 'Generate Programming file':



Select the Create Binary Configuration File check box and click Ok and then select Generate Programming File and right-click on it and then choose 'Run'.



The bitstream file will be created but it takes a moment, again there should be no errors.  Now it's time to connect up the Elbert V2 Development board to your computer via a USB cable - Exciting times...

Then load up the ElbertV2Config program and choose the appropriate communication port for your board - finally navigate to the bitstream file just created, it will be called up_down_counter_module.bin:



Next Click program and the bitstream file will be uploaded to the FPGA:



Once complete you should see a seven segment display light up on the development board:


An animated GIF image of the board in action

Here is the now standard video of the board in operation:


If you wanted to get a bit more experience in coding in VHDL you could modify the program to use all three segments and count up to 999.  You could also implement a simple stop-watch by adding more buttons - the possibilities are endless...

If you need the actual project files for whatever reason, here they are:


This tutorial covers quite a few useful aspects of VHDL - we have covered:
  • Clock dividing
  • Reading a button
  • Driving a seven segment
  • If statements in VHDL
  • Case statements in VHDL 
That's all for now - take care and have fun - Langster!