In this video, I’ll introduce you to VHDL. In one of our previous videos, you learned some basic information about VHDL.
However, in this video, we’ll dive deeper into VHDL’s structure and the features it offers.
More...
What is a Hardware Description Language?
A hardware description language, or HDL, is essentially a tool for describing a digital circuit, which you can then bring into specialized software for implementation, such as Xilinx ISE or Vivado tools.
With an HDL like VHDL or Verilog, you can describe various types of digital hardware for implementation on an FPGA.
Keep in mind that the fundamental concepts and principles in both VHDL and Verilog are the same; only their syntax differs.
VHDL vs. Verilog
While VHDL and Verilog differ in syntax, their underlying principles are the same.
The FPGA Mindset: Hardware vs. Software
It’s crucial to develop the right mindset about what you’re doing before you start coding with an HDL – and, in fact, even while you’re writing code.
As I explained in the video What is an FPGA?, the structure and nature of an FPGA are entirely different from processors.
Therefore, when you’re working with an FPGA and writing VHDL code for it, you always need to consider these differences.
Remember that when you’re coding for FPGA, you’re describing a digital circuit. In other words, you’re not writing a software program.
The FPGA Mindeset
FPGAs are not processors; they are configurable hardware.
When you’re coding for FPGA, you'r describing a digital circuit, not writing a software program.
On the other hand, an HDL has constructs specifically for describing digital hardware, not instructions for writing a software program.
As I mentioned before, commands and instructions are for CPUs; however, an FPGA doesn’t offer any predefined hardware before configuration. It’s essentially just a sea of digital resources like LUTs (Look-Up Tables) and interconnecting wires.
My suggestion to you is that when you start coding for FPGAs, always visualize a digital circuit. As you write each line of code, try to imagine what digital hardware that line is describing.
Gradually, this habit will make it easier for you to intuitively understand how to write code for different hardware structures.
Types of Digital Circuits
You might remember from your digital logic courses that logic circuits generally fall into two categories:
Combinational vs. Sequential Circuits
Combinational circuits are those whose output at any given time depends solely on the inputs at that moment.
Examples of combinational circuits include multiplexers, demultiplexers, decoders, and encoders.
However, sequential circuits differ from combinational ones in that their output at any given time depends not only on the current inputs but also on previous inputs.
This gives us a clue that sequential circuits must contain some memory elements, as they need to calculate the output using both current and past inputs.
Counters and shift registers are well-known, simple examples of sequential circuits.
Synchronous vs. Asynchronous Sequential Circuits
Sequential circuits can be further categorized into synchronous and asynchronous circuits.
Synchronous sequential circuits are those where the input is applied to the circuit in sync with an external signal, typically called a clock signal.
In asynchronous circuits, however, the input is applied to the circuit independently of a clock.
HDLs, including VHDL and Verilog, provide you with the ability to design any of these logic circuits easily.
The Structure of VHDL Code
Alright, let’s dive a bit deeper into the structure of VHDL code and its features.
As I mentioned in a previous video, every VHDL code generally consists of two main parts:
One is the Entity section, where we define input-output ports, and the other is the Architecture section, where we describe the actual circuit.
Understanding the Entity Section
Let’s take a closer look at the Entity structure. The sole purpose of the Entity section is to define input-output ports.
As you see in the example code below, It begins with the keyword Entity. After that, we specify the name of the entity – here, I’ve named it Adder.
Entity Adder is Port ( A : in BIT; B : in BIT; Cin : in BIT; Sum : out BIT; Cout : out BIT ); end Adder;
Next, we use the keyword is, followed by port, and within parentheses, we define the input-output ports. The Entity block concludes with the end keyword and the entity name.
For each port within the Entity, there are three things you need to specify:
First, you define the port name, which is listed in the first column.
After placing a colon, you specify whether the port is an input, output, or bidirectional; in VHDL terminology, this is referred to as the port mode.
Finally, you specify the port type – in this example, I’ve used the bit type. A port with the bit type can take a value of either 0 or 1.
Note that I’ve included this bit type here for demonstration, but in practice, we don’t use it because it’s not synthesizable. Instead, we typically use the std_logic type for single-bit ports, as it’s more suitable for synthesis.
Understanding the Architecture Section
Let’s also get familiar with the Architecture structure. Within Architecture, there are three regions:
The Architecture block begins with the keyword Architecture, followed by the architecture name – here, I’ve used behavioral – then the keyword of, the entity name, and the keyword is. As mentioned, these three regions make up the Architecture section.
The first section, located before the keyword Begin, is the Declarative region, where we define signals and modules.
After the Begin keyword until the end of the Architecture (which ends with End Architecture), we have the Concurrent section. As mentioned, this section is where we define combinational logic circuits.
If you need to describe sequential circuits, you utilize the Process construct within the Concurrent section.
The Process statement itself begins with the keyword Begin and continues to the end of the Process (marked by End Process). This segment, referred to as the Sequential region, is where we define sequential circuits.
An essential point to note is that most of our circuit will be defined within this Sequential region or the Process statement.
Practical Tip
In practice, most circuits you'll design will be synchronous sequential circuits.
As a result, the majority of your VHDL (or Verilog) code will be written within the sequential region, specifically within the Process statement.
Deep Dive into the Concurrent Section
Now, of the three regions I mentioned, let’s take a closer look at the Concurrent section.
Before that, there are a few important points about VHDL to keep in mind.
In VHDL, when you want to assign a signal or a combination of signals to another signal, you use the <= symbol. This operator is known as a signal assignment.
In the following example, you see that we are performing an AND operation on signals B and C and assigning the result to signal A.
A <= B and C;
In VHDL terminology, ports, internal connections like wires, and registers created by the synthesizer are all referred to as signals.
So, whenever I mention a signal, it could mean a port, a wire, or a register that’s been synthesized.
How Assignments Work in the Concurrent Section
The most important rule in the Concurrent section is that an assignment statement only evaluates or updates when one of the signals on its right side changes.
For instance, in the expression B and C assigned to A, this assignment will only be evaluated or applied when either B or C changes.
This behavior is the same as what happens in practice. If you imagine a circuit with an AND gate having two inputs B and C, and output A, A only changes when either B or C changes.
From this, we can conclude that in the Concurrent section, the order of code lines relative to each other doesn’t matter. I’ll explain this concept in more detail as we continue.
The #1 Rule of the Concurrent Section
In the Concurrent section, signal assignments are evaluated and updated only when a change occurs on one of its right-hand side signals.
Unlike traditional programming languages, the order of assignments in the Concurrent section does NOT affect the circuit's behavior.
Understanding Events in VHDL Coding
Let’s also get familiar with another term here: Event.
In VHDL, when the value of a signal changes, we say an event has occurred on that signal.
So, with this terminology, if we restate the Concurrent rule, it’s that an assignment statement will only be evaluated or applied when an event occurs on one of the signals on its right side.
Example: Building an Adder in VHDL
Now, let’s explore the Concurrent section in the ISE software a bit more.
Here, you see the same Adder example we discussed in one of the previous videos.
In the first part, we define the Entity.
Then there is the Architecture section, where we’ve defined two outputs, Sum and output carry (or Cout).
To keep things simple, I’ll temporarily remove these two assignments (i.e., Sum and Cout) and replace them with two more simple assignments as you can see in the code below:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity Example_01_Full_Adder is Port ( A : in STD_LOGIC; B : in STD_LOGIC; Cin : in STD_LOGIC; Sum : out STD_LOGIC; Cout : out STD_LOGIC ); end Example_01_Full_Adder; architecture Behavioral of Example_01_Full_Adder is begin A <= B and C; D <= A; end Behavioral;
We’re performing an AND operation on signals B and C and assigning the result to A. Then, we assign signal A to D.
As I mentioned, in the Concurrent section, each assignment line only evaluates and updates when there’s an event on one of the right-side signals. In other words, if either B or C changes, this assignment will be evaluated, updating the value of A.
In the following line, if A changes, the assignment to D will then be evaluated, updating D’s value.
Now, if we change the order of these lines, moving the second assignment before the first one, would that affect the circuit’s operation?
D <= A; A <= B and C;
As I mentioned, in the Concurrent section, which is everything from the Begin keyword of the Architecture section to End Architecture, the placement of assignments relative to each other has no priority.
Whether you write one line at the beginning or the end of the code, it doesn’t impact the circuit’s behavior.
This is unlike traditional programming languages. Here, rearranging lines does not change the function of the circuit.
The only factor that causes these assignments to be evaluated and the right-hand value to be applied to the left-hand side is an event occurring in one of the right-hand side signals.
In this case, if an event occurs on either signal B or C, the line assigning B and C to A will be evaluated, and the value of A will be updated.
Since A has changed, it triggers an event on the right side of the next assignment, causing that line to be evaluated, which updates D.
So, in both scenarios, the assignment of A to D is the second line to be evaluated, regardless of whether I write it as the first or second line of code.
Now, let’s go back to our Adder example. As you can see in the code below, we have two assignments.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity Example_01_Full_Adder is Port ( A : in STD_LOGIC; B : in STD_LOGIC; Cin : in STD_LOGIC; Sum : out STD_LOGIC; Cout : out STD_LOGIC ); end Example_01_Full_Adder; architecture Behavioral of Example_01_Full_Adder is begin Cout <= (A and B) or (A and Cin) or (B and Cin); Sum <= A xor B xor Cin; end Behavioral;
In the first line, we define the Sum output, and in the second line, we define Cout. These two assignments are entirely independent, and the order in which we write these lines doesn’t affect the circuit’s operation.
The only reason an assignment will be evaluated and the value on the right applied to the left is if there’s a change or an event on one of the right-side signals.