VHDL Episode 1 - A simple adder
Previous episodes: * /en/blog/ac/vhdl0
It took me long to start this tutorial, but I had a noble excuse: I was finishing my B.Sc thesis (in Computer Science)… But enough with excuses, let’s start the VHDL coding! In the first episode of our tutorial we deal with a binary adder.
In case you don’t know what a binary adder is, the world’s no. 1 source of information (Wikipedia) is always there to help you: Binary Adder. Our first hardware block described using VHDL is going to be a 1-bit Full Adder:
And to show that VHDL isn’t such an ugly monster, here’s the complete adder’s code:
library ieee;
use ieee.std_logic_1164.all;
entity fullAdder is
Port(
a : in std_logic;
b : in std_logic;
cin : in std_logic;
s : out std_logic;
cout : out std_logic);
end fullAdder;
architecture arch of fullAdder is
signal aXorB : std_logic;
begin
aXorB <= a xor b;
s <= aXorB xor cin;
cout <= (a and b) or (cin and aXorB);
end arch;
So let’s first talk a bit about the code just above and then write and run some tests to show that this amazing adder works!
During the following episodes I’m going to show you the VHDL coding style I adopt. The first tidbit of this style is that each hardware block resides in a similarly named text file. Our fullAdder will then reside in fullAdder.vhd
.
Each VHDL file has TWO main sections: entity
and architecture
. The entity section describes the block’s interface to the outside world, the Port(..
construct means exactly what you think it means: it’s a list of the block’s input and output ports. Each port has a name, a direction and a type. By the way, EVERYTHING in VHDL has a type, some people love it, some hate it, nobody remains indifferent :)
The datatype we are using in all of our adder’s ports is std_logic
, a logic type which besides ‘0’ and ‘1’ has some other possible values (high impedance, undefined and others I never used). The std_logic
type and its std_logic_vector
partner are by far the most commonly used VHDL types.
Now a bit about the second section of all VHDL files: the architecture. In this section we define all the building blocks for our circuit, how to interconnect them and which signals to use for these connections. Just before the begin
keyword we need to declare everything that’s going to be used in the architecture; in our case we declare only one signal:
signal aXorB : std_logic;
Signals can be thought of as wires, or buses. They also have a name and type, but no direction. After all declarations we start the body of the architecture itself. In our architecture for a full adder we do three parallel assignments. Each of them binds a signal (or port) to an expression. Because the assignment is parallel, the signal (or port) is bound to the expression forever, and if you try to bind a signal more than once the VHDL compiler will complain - it means you are causing a short-circuit :) The parallel assignments in our architecture are just a copy of the logic formulas defining a full adder (according to Wikipedia)
S = A xor B xor Cin
Cout = (A and B) or (Cin and (A xor B))
The coding style that we can infer from this first episode follows:
- In general:
- All code blocks between “begin” and “end” (or “is” and “end”) are indented, always using 4 spaces.
- Port names and signal names are written in
camelCaseWithFirstLower
.
- In the
entity
section:- The “Port” keyword is indented in relation to the “entity”, and the port list itself is indented in relation to the
Port(..
. - All semicolons in the port list are aligned vertically.
- The “Port” keyword is indented in relation to the “entity”, and the port list itself is indented in relation to the
- In the
architecture
section:- In a sequence of assignments, we align all arrows (<=) vertically.
This is a VHDL coding style which I personally find beautiful and readable. So please don’t start the flame war :)
After the style talk we start the final part of this episode: showing that our design works (testing it) \o/ First of all, let’s run GHDL just to confirm that our code’s syntax is OK:
ghdl -s fullAdder.vhd
Now for the really fun part: we’ll write a testbench for our adder. From now on, for every hardware block that we write, we’ll have a corresponding testbench. A testbench is just a VHDL file providing some inputs to the design under test (DUT) and checking that the outputs are as expected. You can find the testbench for our fullAdder here: http://hpaste.org/47881
The testbench’s code is big for this case, and surely there are ways to make it smaller and prettier. Anyways, I chose to write it this way to show the general guidelines that all next episodes will follow.
The entity
section in a testbench is always empty, and in the architecture
section there’s a certain pattern:
- Declarations:
- First we declare the
component
being tested, we simply copied the port list from thefullAdder.vhd
file. - Then we have one signal declared for each port of the component being tested.
- The
for dut: fullAdder...
is a sort of GHDL specificity and we can ignore it for now…
- First we declare the
- Test cases:
Just after the
begin
keyword we instantiate the component under test. It means we bind its ports to the signals we just declared. These are the signals we are going to monitor on the testbench.Inside the testbench
process
there are several test cases, each with expected and actual outputs
As in our design (fullAdder) there are only 8 possible input combinations (3 bits), we tested all of them. This is usually not the case, and hardware designers test only the combinations which they consider most “significant” to demonstrate the block’s functionality. Each test case has approximately the same format:
sig_<0> <= value_<0>;
...
sig_<1> <= value_<1>;
wait for x ns;
assert (expected_output = actual_output) report "test_name" severity failure;
We give a value for each of the circuit’s inputs, then wait for some time (so that the outputs are stable) and check whether the actual output equals the one we expected…
Well people, that’s all for today! The first episode of the tutorial was pretty lengthy because a lot of concepts had to be introduced “from scratch”. Here we go with some exercises!
Exercises for today’s episode:
Download the file VHDL1, unzip it, run the simulation and view the generated
trace.vcd
file in GTKWave. The includedMakefile
shall help youModify some tests, make them fail and then correct them… Hack the code at will :) The same file organization and the same style will be used in all following episodes…