From Hamsterworks Wiki!
Please make sure that you have completed the earlier modules of FPGA course.
Having problems with your code? Do I need to change something? Want a reference solution to the project? Email me at "firstname.lastname@example.org", after removing the "nospam-" bit. I'll try to get back to you in a day or so.
Aims of module
- Implement a binary counter
- Make use of internal storage
- Make use of a clock signal
- Make use of a VDHL process
- Make your first project that 'knows' the passage of time
Up to now all our project has been pure combinatorial logic - the output signals of the circuit is just a function of its inputs, and it has no internal state information (i.e. no storage elements ("flipflops") are used. As you may have discovered the order of the statements made no difference to the design - all assignments were active all the time. We are now going to use some storage elements, and this requires use of a clock signal.
A clock signal is an input that has regular transitions from high to low, and is therefore very useful for keeping all the parts of a design in sync. The Basys2 board has a clock signal that can run at either 10,000,000, 25,000,000, or 100,000,000 cycles per second (10MHz, 25MHz or 100MHz) although later on we will see how the FPGA can be used generate other frequencies from.
This modules projects will be based around a binary counter running at 25MHz, and as 25,000,000 is quite a big number (a million times faster than what human eyes can see) we need some way to slow it down. The easiest way is to create a 28 bit counter, and only show the top eight bits on the LEDs.
But first we need to know about processes and the "IF" statement.
From a designer's point of view a VHDL process is a block of statements that run sequential, and are triggered when any of the signals that it is sensitive to change value.
Here is a process called 'my_process' that is sensitive to two signals (input1 and input2):
my_process: process (input1, input2) begin output1 <= input1 and input2; end process;
Important note on sensitivity lists
- An event (change of value) on the the signals listed in the sensitivity list is what triggers the process.
- For purely combinatorial logic it should be all 'inputs'. Also, none of the signals assigned within the process should be in its sensitivity list, or it will be evaluated multiple times during simulation.
- For a 'clocked' processes the sensitivity list should be the clock signal and all asynchronous signals (i.e usually the clock signal and maybe an async reset).
- If you don't follow these rules you will get lots of odd behaviors in simulations your process will be triggered when you don't expect, or fail to trigger at all. When you try to implement the design in hardware it will fail to work anything like it did in simulation.
The usefulness of processes is in them allowing you to use sequential statements, the most useful of which is the 'IF' statement.
- VHDL has an "if" statement, much like any other language. Ths syntax is:
if [condition] then statements; end if;
if [condition] then statements; else statements; end if;
Remember that "if" statements can only be used inside processes block - if you attempt to use them outside of a process you will get a compilation error.
Also remember that there is a ';' following the "end if" statement!
Detecting the rising edge of a clock
It is customary to synchronise transitions to the rising edge of a clock. The easyest way to do this is to use the "rising_edge" function:
if rising_edge(clock_signal) then [statements] end if;
Another common way that you might see is to test the "event attribute" of the clock signal, which evaluates to true if this signal is the one that triggered the process to be evaluated, and then check that the clock signal is '1'. Together this tests the detect the rising edge of the clock:
if clock_signal'event and clock_signal = '1' then [statements] end if;
Although common in text books the use of clock_signal'event and clock_signal = '1' is now discouraged, as it assumes that the clock signal was a '0' before the event was triggered.
Declaring storage elements
Storage elements are declared just like a local signal. It is how you use them that implicitly makes them act as storage. If a signal only gets assigned during a clock transition it will be implemented using flip-flops:
... architecture behavioural of counter signal counter : STD_LOGIC_VECTOR(7 downto 0); begin count: process(clock) begin if rising_edge(clock) then counter <= counter+1 end if; end process; end architecture; ...
The other situation that triggers a signal to be implemented as a flip-flop is when not all paths through a process assign it a value:
count: process(clock) begin if rising_edge(clock) then if switch1 = '1' then if switch2 = '1' then output_signal <= '1'; else output_signal <= '0'; end if; end if; end if; end process;
A flip-flop will be assigned because 'output_signal' has to hold its value when switch1 changes from '1' to '0'.
And as with programming languages, it is always good practice to assign an initial value to your storage elements:
signal counter : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
or perhaps more conveniently when working with larger signals:
signal counter : STD_LOGIC_VECTOR(7 downto 0) := (others => '0');
It is not at all safe to assume that uninitialised signals will be 'zero' - when you reset your project the internal flip-flops may hold their last used values!
So here is the finished 8 bit counter:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity Switches_LEDs is Port ( switches : in STD_LOGIC_VECTOR(7 downto 0); LEDs : out STD_LOGIC_VECTOR(7 downto 0); clk : in STD_LOGIC ); end Switches_LEDs; architecture Behavioral of Switches_LEDs is signal counter : STD_LOGIC_VECTOR(7 downto 0) := (others => '0'); begin clk_proc: process(clk) begin if rising_edge(clk) then counter <= counter+1; end if; end process; end Behavioral;
(Note: Currently the project doesn't use the 'LEDs' output - if you build using this code as-is the counter won't have any useful effect and will be optimized out, leaving you with an an empty design!).
Project 6.1 - Binary up counter
- Using the above template, extend the project to use a 30 bit counter ("29 downto 0"), displaying the top 8 bits on the LEDs. Remember to add a new constraint attaching that forces the 'clk' signal to the correct pin for board's "clock" signal (B8 on the Basys2).
This is the constraint for the Basys2 board (untested):
NET "Clk" LOC = "B8"; NET "Clk" CLOCK_DEDICATED_ROUTE = FALSE;
This is the constraint for the Nexys2 board:
NET "Clk" LOC = "B8";
This is the constraint for the Nexys3 board:
Net "clk" LOC=V10 | IOSTANDARD=LVCMOS33; Net "clk" TNM_NET = sys_clk_pin; TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 100000 kHz; #100000
Project 6.2 - Binary down counter
- Change the project to count down
Project 6.3 - Binary up/down counter
- Use one of the switches to indicate the direction to count
- Change the project to counts accurately in (binary) seconds
- Change the project to time how long it takes to switch a switch on and off - you will need a second switch to reset the counter too!
Ready to carry on?
Click here to carry on to the next module.