Using VHDL Process Blocks to Model Sequential Logic

In this post, we look at the some of the techniques we can use to model sequential logic circuits in VHDL. We mainly look at the process block which we use to write VHDL code which is executed sequentially. We will look at some of the fundamental features of the process block, including sensitivity lists, variables and assignment scheduling.

In the post on VHDL logical operators and signal assignment, we talked about the concept of combinational logic circuits. We also saw how we can use concurrent statements to model the behavior of these circuits in VHDL.

As concurrent statements execute in parallel, they are not suitable for the modelling of sequential logic circuits. Therefore, the VHDL programming language features a construct known as the process block which we can use to model these circuits.

We can also use process blocks to model combinational logic. However, we often have to write more code in comparison to concurrent statement.

As with the previous blogs, there are a number of exercises at the end of this post. However, you should consider reading the blog on basic testbenches in VHDL before tackling these exercises. This will allow you to simulate your solutions and prove that they are working as desired.

VHDL Processes

We use the VHDL process keyword to create blocks of code which are executed sequentially. This is especially important when we describe sequential circuits as we must describe behavior which occurs in a specific sequence.

The code snippet below shows the general syntax for the VHDL process.

<label>:
process (<sensitivity_list>) is
begin
  -- Functional code
end process <label>;

The statements in the process construct execute one after another. This concept is likely to be quite familiar as it is the way in which conventional programming languages such as C or Java work.

However, we need to be careful when using process blocks as there are some features which are unique to VHDL.

These are connected to the fact that we are describing hardware rather than writing software.

It is important that we have a good understanding of process blocks in order to be an effective VHDL designer.

VHDL Sensitivity List

When we write a process block in VHDL, each line of the code is run in sequence until we get to the end of the block.

If we include a sensitivity list in our process, our VHDL code waits at the end of the block until there is an event on one of the signals in this list.

When a relevant event is detected, the process block resumes executing from the first line of code.

We can see then that the sensitivity list has a big effect on the way our process blocks function in VHDL.

It is also possible to exclude the sensitivity list from our process, in which case the VHDL code inside the process block will run continuously.

This means the program loops back to the start of the block after executing the last line.

However, this is not representative of a circuit, which would remain in a steady state until one of the inputs changes state.

This is the behavior that the sensitivity list attempts to emulate in VHDL.

Simple VHDL Process Example

Let’s consider the D type flip flop as an example to show how we use the process block to model sequential circuits in VHDL.

As the output changes state whenever there is a positive clock edge, we must include the clock signal in our sensitivity list. The output (q) is then assigned the value of the input (d) on each rising edge of the clock.

The code snippet below shows the implementation of this component.

d_flip_flop:
process (clock) is
begin
  if rising_edge(clock) then
    q <= d;
  end if;
end process d_flip_flop;

As we have included the clock signal in our sensitivity list, the code above executes every time the clock changes from 1 to 0 or from 0 to 1.

The rest of the time, the code is in a paused state while it waits for a change of state to occur.

However, we only expect the output of the flip flop to change when the clock changes from 0 to 1. To detect when this has occurred, we use the rising_edge macro together with an if statement. By doing this we ensure that the output is only updated on a rising edge.

The VHDL if statement is an example of a sequential statement which we discuss further in a separate post.

We should only ever use the rising edge macro for clock signals as synthesis tools will attempt to utilize clock resources within the FPGA to implement it.

Assignment Scheduling

Although the code in a process is sequentially executed, it is important to understand that signals are not updated in this way.

To demonstrate why this is the case, let’s consider the simple twisted ring counter circuit below.

A simple twisted ring style counter circuit
divider:
process (clock) is
begin
  if rising_edge(clock) then
    q_dff1 <= not q_dff2;
    q_dff2 <= q_dff1;
  end if;
end process divider;

First, let’s look at the behavior if the signals did update immediately.

Let’s assume that the output of both flip flops is 0 when a clock edge occurs. As a result of line 5 in the code above, the output of DFF1 changes to 1.  We can then see that the next line of code would set the output of DFF2 to 1.

This is clearly not the intended behavior of the circuit we are modelling. Instead we expect DFF2 to remain at 0 and DFF1 to change to 1. 

To overcome this issue, VHDL uses scheduled assignment for signals in a process block. As a result, signal changes don’t occur immediately after assignment but are instead scheduled to occur at a future time.

Normally signals in a process block update their value at the end of a simulation cycle, which refers to the time taken for the simulator to execute all of the code for a given time step. However, it is also possible for signals to update their values in a subsequent delta cycle.

To better demonstrate the way scheduled assignment works, let’s again consider the simple dual flip flop circuit.

The simulator firstly executes the statement to update DFF1 and schedules the update to the signal value.  

The simulator then runs the second line of code, using the original value of the DFF1 flip flop and schedules the update of DFF2.

As there are only two statements in this design, the simulation cycle is now complete. At this point, all of the scheduled changes are applied and the flip flops have the correct values.

Variables in VHDL

We can also declare variables within our VHDL process blocks.

We use VHDL variables in the same way as we would variables in other programming languages such as C. This means that, unlike VHDL signals, they always update their value immediately after assignment.

We assign variables a value using the := symbol rather than <=.

The code snippet below shows how to declare and assign a variable within a process.

example_process:
process (clk) is
  variable var_ex : std_logic;
begin
  var_ex := '1';
end process;

We often use variables to improve the readability of our VHDL code.

For example, if there is a long boolean equation it can make sense to break the expression down for readability.

We can also use variables to keep the code which models the combinational and sequential circuits together.

Although we mostly use variables to model combinational logic, variables can also be synthesised as flip flops. This occurs if we write code in which we read the variable before we assign a value to it.

Variable vs Signal in VHDL

There are two major differences between variables and signals in VHDL.

Firstly, we can only use a variable within a process block whereas signals can be used in any part of the VHDL design.

Secondly, the value of variables are updated immediately after assignment whereas signals inside process blocks aren’t.

We have seen in the previous section how we declare a variable within a VHDL process block.

When we do this, we can only read or write the variable within the process block that it was declared in. As a result of this, we can’t access the variable in any of the other processes or concurrent statements in our code.

In contrast, we can read signals in any process or concurrent statement which we write. However, we can still only assign signals at one point in our code. This means we can only write data to a signal within one concurrent statement or process block.

We can actually use a special type of variable, known as a VHDL shared variable, which can be read and written by all the processes in our VHDL code. However, this is a more advanced topic which we discuss in detail in a later post.

As we talked about in the previous section, when we write sequential logic circuits our signals use assignment scheduling to update their values.

This is intended to emulate the behaviour of storage elements such as flip flops.

In contrast to this, variables are assigned immediately after they are assigned a value.

When we write combinatorial logic circuits using concurrent statements, the signal value will update immediately on assignment.

As we can only use variables within processes, there is no equivalent for using variables with concurrent statements.

Combinational Logic in a VHDL Process

Although we most commonly use process blocks to model sequential logic in VHDL, we can also use them to model combinatorial logic.

As this adds boiler plate code to our design, we don’t see much code written in this way. Boiler plate code refers to code we must include in a design even though it serves no functional purpose. We have to type more lines of code because of this.

The code snippet below shows how we would model ancombinational AND-OR circuit using the VHDL process block. This is the same circuit which we model using concurrent statements in the post on VHDL logical operators.

combinatorial_example:
process (all) is
begin
  logic_out <= (a and b) or c;
end process combinatorial_example;

We can see that this code is almost identical to the example in the previous. The only difference here is the fact that it’s encased within a process statement.

The “all” statement in the sensitivity list performs an important function here.

Our simulator or synthesis tool will determine which signals to include in the sensitivity list when we use this keyword.

The all keyword was introduced in VHDL-2008 and can not be used with earlier standards of VHDL. Before this, we had to manually include the relevant signals in the sensitivity list which often leads to bugs in the code.

As combinational circuits written inside processes are generally more difficult to maintain, we should avoid doing this as far as possible.

However, we can sometimes simplify the modelling of complex combinational logic in VHDL by using a process block.

Exercises

Why do we use process blocks in VHDL to model sequential logic circuits?

They allow us to write code which is executed sequentially

What are sensitivity lists used for in a process block?

They define the list of signals that a process will wait on before resuming the execution of code.

Which operator to we use to assign values to a variable and which operator do we use to assign a value to a signal?

:= for variables and <= for signals 

Write the code for a 4 input NAND gate using a process block

-- Using VHDL-2008 syntax
nand_example:
process (all) is
begin
  nand_out <= a nand b nand c nand d;
end process nand_example;
 
-- Using non VHDL-2008 syntax
nand_example:
process (a, b, c, d) is
begin
  nand_out <= a nand b nand c nand d;
end process nand_example;

Write the code for the circuit shown below.

Circuit diagram showing 3 d type flip flops in a chain. The input to the first flip is an or gate whose inputs are the outputs of the other two flip flops.
ff_example:
process (clock) is
begin
  if rising_edge(clock) then
    q_dff1 <= q_dff2 or q_dff3
    q_dff2 <= q_dff1;
    q_dff3 <= q_dff2;
  end if;
end process ff_example;