Skip to content

Latest commit

 

History

History
executable file
·
326 lines (266 loc) · 11 KB

content.asc

File metadata and controls

executable file
·
326 lines (266 loc) · 11 KB

Using a clock signal

Up to now our project has been pure combinatorial logic-- the output signals of the circuit are just a function of its inputs, and it has no internal state information (i.e. no storage elements 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. Now that we can perform addition we should be able to make a project that implements a counter. However, one thing is missing-- the ability to track the passage of time.

This is the big difference between designing in VHDL and programming. In a program there is the "thread of execution" and its associated state information-- the program counter, the stack pointer, the contents of registers and so on. In VHDL there isn’t.

What is needed is somewhere to store the values of signals and some way of synchronizing when these stored values should change. To progress our designs further we need flip-flops and a clock signal.

Flip-flops

A flip-flop stores one bit of information, and this stored bit is updated when its "Clock Enable" signal is asserted, and the desired transition occurs on the 'clock' signal-- either from '1' to '0' (a falling edge) or from '0' to '1' (a rising edge).

Clock signals

A clock signal is an input that has regular transitions between the low and high state, and is therefore very useful for keeping all the parts of a design in sync. The Papilio One has a clock signal running at 32,000,000 cycles per second (32MHz), whereas the Basys2 board has a clock signal that can run at either 25,000,000, 50,000,000 or 100,000,000 cycles per second (25MHz, 50MHz or 100MHz). This is not as big a difference between boards as it sounds, because later on we will see how the FPGA can be used to generate other frequencies from this reference clock.

This chapter’s projects will be based around a binary counter running at 32MHz (so when a Basys2 is used it will run about 50% quicker). As 32,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 VHDL processes and the "IF" statement.

VHDL Processes

From a designer’s point of view, a VHDL process is a block of statements that operate sequentially and is 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;
Note
  • Any event (change of value) on the signals listed in the sensitivity list is what triggers the process.

  • For purely combinatorial logic it should be all 'inputs', and 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' process the sensitivity list should be the clock signal and all asynchronous signals-- 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 as 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 that they allow you to use sequential statements, the most useful of which is the 'IF' statement.

IF statements

VHDL has an "if" statement, much like any other language. This syntax is:

  if [condition] then
    [statements]
  end if;

and

  if [condition] then
    [statements]
  else
    [statements]
  end if;

Remember that "if" statements can only be used inside a process block - if you attempt to use them outside of a process you will get compilation errors. Also remember that there is a ';' following the "end if" statement!

VHDL supports all the normal comparisons you would expect-- just be aware that "not equals" is "/=" - very strange!

Tip
Coding style tip

If you are used to C, you might be tempted to use something like the following to implement a counter:

 if(counter < counts-1)
   counter++;
 else
   counter=0;

This is bad form as it is a comparison of value, and not a test for equality. The tools might implement this using a "math" function, rather than a logic function. Due to the time that \'carries' take (about 0.05ns per bit) this may lower your design’s performance and increases resource usage.

If you can ensure that the value of "counter" stays between "0" and "counts-1" then it is far better to use the VHDL equivalent of the following:

 if(counter == counts-1)
   counter=0;
 else
   counter++;

This is because test for equality are much quicker as the carry chain is not used.

Detecting the rising edge of a clock

Any of the normal tests (equality, inequality…​) used in programming can be used to test values against each other. If we use these tests on our signals we usually end up generating combinatorial logic. For example:

  select_switch: process(switch(0), switch(1), switch(2))
    begin
     if switch(0) = '1' then
       result <= switch(1);
     else
       result <= switch(2);
     end if;
  end process;

This could equally be implemented with the following concurrent (always active) statement:

  result <= (switch(1) and switch(0)) or (switch(2) and not(switch(0)));

We can also look for a signal’s transition as the condition that triggers something to happen. The easiest way to do this is to use the "rising_edge" function:

  if rising_edge(clock_signal) then
     result <= switch(1);
  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 also check that the clock signal is '1'. Together these tests can detect the rising edge of the clock:

  if clock_signal'event and clock_signal = '1' then
     [statements]
  end if;

Although common in older textbooks, the use of "clock_signal’event and clock_signal = \'1\'" is now discouraged. It assumes that the clock signal was a '0' before the event was triggered, and can cause problems during simulation.

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 to hold 'output_signal' to keep its value when switch1 changes from '1' to '0'.

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 usually safe to assume that uninitialized signals will be 'zero', however simulations will show the signal as being ''undefined'', as will the result of any operations performed on that signal.

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;
Warning
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 empty design!

Project - 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 that forces the 'clk' signal to be assigned the correct pin for your FPGA board’s "clock" signal.

# Constraints for Papilio One
NET "clk" LOC = "P89" | IOSTANDARD = LVCMOS25 ;
# Constraints for the Basys2
NET "Clk" LOC = "B8";
NET "Clk" CLOCK_DEDICATED_ROUTE = FALSE;

Project - Binary down counter

  • Change the project to count down

Project - Binary up/down counter

  • Use one of the switches to indicate the direction to count.

Challenges

  • Change the project to count accurately in (binary) seconds

  • Change the project to time how long it takes to turn a switch on and off - you will need a second switch to reset the counter too!