VHDL
When doing digital design, there is more than one way to skin a cat. This
expression should make you happy regardless of your feelings towards
cats because it means that your personality can be expressed through
the code that you write. In Lecture 1, we examined a structural
method of describing a circuit; we transformed the circuit diagram
into a textual description. Today, we will examine another method of
VHDL coding: behavioral.
Structural
- Using the paradigm of the Structural view, a circuit is constructed by using smaller parts.
- A Structural description, therefore, specifies the types of parts and connections.
- Essentially, a structural description is a textual description of a schematic.
- Structural descriptions are done by using the "component" keyword in VHDL. Components are:
- First declared (made known)
- Then instantiated (used)
The majority circuit, using components, can be seen coded in VHDL below.
------------------------------------------
-- Author: Prof Jeff Falkinburg
-- Date: Spring 2017
-- Purp: Structural Majority circuit
------------------------------------------
library IEEE; -- These lines are similar to a #include in C
use IEEE.std_logic_1164.all;
library unisim; -- Use these libraries if you are using primitive components
use unisim.vcomponents.all;
entity majority is
port( a, b, c: in std_logic;
f: out std_logic);
end majority;
architecture structure of majority is
component AND2
port ( i0, i1 : in std_logic;
o : out std_logic);
end component;
component OR3
port ( i0, i1, i2 : in std_logic;
o : out std_logic);
end component;
signal s1, s2, s3: std_logic; -- wires which begin and end in the component
begin
unit1: AND2
port map ( -- s1 <= a and b;
i0 => a,
i1 => b,
o => s1);
unit2: AND2
port map ( -- s2 <= b and c;
i0 => b,
i1 => c,
o => s2);
unit3: AND2
port map ( -- s3 <= a and c;
i0 => a,
i1 => c,
o => s3);
unit4: OR3
port map ( -- f <= s1 or s2 or s3;
i0 => s1,
i1 => s2,
i2 => s3,
o => f);
end structure;
Behavioral
A behavioral description of a component describes what the circuit
does rather than how it is done. For example, we will consider the
majority circuit from lecture 1 in the code below.
------------------------------------------
-- Author: Prof Jeff Falkinburg
-- Date: Spring 2017
-- Purp: Behavior Majority circuit
------------------------------------------
library IEEE;
use IEEE.std_logic_1164.all;
entity majority is
port( a, b, c : in std_logic;
f : out std_logic);
end majority;
architecture behavior of majority is
begin
f <= '0' when a='0' and b='0' and c='0' else
'0' when a='0' and b='0' and c='1' else
'0' when a='0' and b='1' and c='0' else
'1' when a='0' and b='1' and c='1' else
'0' when a='1' and b='0' and c='0' else
'1' when a='1' and b='0' and c='1' else
'1' when a='1' and b='1' and c='0' else
'1';
end behavior;
It is important to note that the single (very long) statement in the architecture
is a concurrent signal assignment (CSA) statement. This means that this statement is
simply another hardware statement and can be mixed together with
some ANDs and some component instantiation statements.
Coding using the behavioral architecture, while much more intuitive than the combinational
logic description that was presented in lecture 1, still has some
room for improvement. We could take advantage of the concatenation
operator to make the code more readable. The idea is to replace
the (a='0' and b='0' and c='0') statement with something like
(temp = "000"). This can be done by creating a local variable
(a signal) which is just the concatenation of the three input
variables. This temporary variable will then be used in the
"when" statement. The new streamlined architecture for the majority
circuit is shown below.
architecture behavior of majority is
signal temp: std_logic_vector(2 downto 0);
begin
temp <= a & b & c;
f <= '0' when temp = "000" else
'0' when temp = "001" else
'0' when temp = "010" else
'1' when temp = "011" else
'0' when temp = "100" else
'1' when temp = "101" else
'1' when temp = "110" else
'1';
end behavior;
A note on literals
Notice that individual bits are surrounded with single quotes
and that std_logic_vectors are surrounded with double quotes.
Any multi-bit literal must be surrounded by double quotes. Another important note is that
you can specify hexadecimal literals by putting an x in front
of the constant. So for example, x"4E" is a valid VHDL literal.
Component Instantiation
In order for VHDL to be useful in the design process, it must allow
hierarchical design - the ability to include a design unit as a component
in a higher level design component. This is called creating an
instance of the entity. Hierarchical design allows us to abstract away
some of the details of a design and focus on the high level behavior.
This is the same concept as writing subroutines in a high level language.
The reason to bring this concept up now is because we use VHDL modules to
test our VHDL designs. In the following discussion, we are going to
build a module to test our majority circuit developed above.
The VHDL module that will do the testing of the majority circuit
is called a testbench. Inside this testbench, we will create an
instance of the majority circuit and apply signals to the majority
circuit and check to see if the circuit responds with the correct
value. It is important to state up front that
testbenches are
only created for simulations; they are not intended to be synthesized
onto the fabric of an FPGA.
In order to instantiate an instance of the majority circuit inside
the testbench, you will need make 2 declarations; specify the entity
description of the component (lines 4-9 below) and create an instance of
the component (line 12-16 below).
1. ENTITY majority_tb IS
2. END majority_tb;
3. ARCHITECTURE behavior OF majority_tb IS
4. COMPONENT majority
5. PORT( a : IN std_logic;
6. b : IN std_logic;
7. c : IN std_logic;
8. f : OUT std_logic);
9. END COMPONENT;
10. signal s1, s2, s3, s4: std_logic;
11. begin
12. uut: majority PORT MAP (
13. a => s1,
14. b => s2,
15. c => s3,
16. f => s4);
17. end
Let's discuss these two steps further. The first, declaring the entity description
of the entity to be instantiated (lines 4-9), is really quite easy. Just
replace the term "entity" in the description of the majority circuit with the
term "component". Note that this is done inside the architecture of the
testbench. The second will require some more explanation, aided by the
following picture.
The description of the majority circuit defines what goes on
inside the architecture. In terms of the picture above,
this includes all the gates and wires in the box labeled "uut:majority".
the variables a,b,c,f inside this box are the port variables in
the entity description of the majority circuit. The box labeled
majority_tb is the testbench which creates an instance of the
majority circuit. The signals labeled s1, s2, s3, s4 are created
inside the testbench circuit and used to communicate to the
majority circuit. The relationship between these signals and the
signals inside the majority circuit are described by lines
13,14,15,16. For example, line 13 states that the signal a
inside the majority circuit is connected to s1 outside the circuit.
One final note about line 12, the one that declares the instance of
the majority circuit: Each instance of an entity should be given
a distinct name. This is because we will frequently need to create
several instances of the same component. For example, when creating
a 4-bit adder, we will require 4 instances of a full adder. This is
what the "uut" label is for, it uniquely identifies the instance of
the majority circuit for the compiler.
You may have noticed that the testbench does not have any signals
in its entity description. This is because the testbench will
contain code which drives the signals going to the majority circuit.
We will go into this further in the next section.
Simulation and Testbench
You have been provided an example testbench at the top of this page in
lec02_tb.vhdl. We have already discussed how to instantiate the majority
circuit. Now for the part about applying signals to the majority circuit.
Just inside the architecture statement, you should see the following four
lines of code. Line 1 just makes a constant, the value of TEST_ELEMENTS
cannot be changed. Lines 2 and 3 create a pair of new data types. Line
4 is really the business, creating an array of test values that are going
to be applied to the majority circuit. In this case, it is just every
combination of 3-bits. A similar array is defined, called TEST_OUTPUT,
which contains the corresponding output for each of these inputs.
These two arrays are used in the body of the testbench.
1. CONSTANT TEST_ELEMENTS:integer:=8;
2. SUBTYPE INPUT is std_logic_vector(2 downto 0);
3. TYPE TEST_INPUT_VECTOR is array (1 to TEST_ELEMENTS) of INPUT;
4. SIGNAL TEST_INPUT: TEST_INPUT_VECTOR := ("000", "001", "010", "011", "100", "101", "110", "111");
I want to take this opportunity to reiterate that test benches are not
synthesized onto the Xilinx chip, so consequently, the coding style used
should not be used on any circuit that you intend to synthesize.
Now, for the business end of the testbench shown in the following code snippet from
lab02.vhdl, The main purpose of the loop below is to apply each of the
8 TEST_INPUT vectors to the majority circuit and check that the output is
correct. Line 2 is where the array is read. Elsewhere in the body of the
architecture, testVector is broken into individual std_logic bits and
applied to the instantiated majority circuit. The delay in line 3 allows
the circuit outputs to settle. Line 4 is a curious artifact only available
in a simulation; if the output of the majority circuit, f, does not equal
test_output(i), the value that the majority should equal, then the code
in lines 5 and 6 is executed. Line 5 prints an error message in the console
area of ISim. Line 6 halts the simulation.
1. for i in 1 to TEST_ELEMENTS loop
2. testVector <= test_input(i);
3. wait for 1 us;
4. assert f = test_output(i)
5. report "Error in majority circuit for input " & integer'image(i)
6. severity failure;
7. end loop;
In class, we will experiment with the simulator. Some things I would like
to go over are:
- How to add and remove waveforms to the waveform view.
- How to change the radix of a vector waveform
- How to change the colors of the waveforms.
- How to transcend the design hierarchy.
- How to observe signal values in design hierarchy.
- How to observe signals values in the VHDL code.
- How to save a load a simulation waveform wcfg file.