In the field of Programmable Logic Devices (PLD), larger units and standard programming
languages have emerged to the forefront. Devices like Field Programmable Gate Arrays (FPGA) and Complex
Programmable Logic Devices (CPLD) have made it possible to design and implement complete processing systems onto
one or two integrated circuit devices. A language to describe the logic hardware was developed to facilitate this design process.
That language is VHDL (Very high speed integrated circuit (VHSIC) Hardware Description Language )
Several manufactures of FPGA and CPLD devices as well as third party companies have created application programs
using VHDL to aid in designing and implementing these custom logic systems. Among the leaders in the field and their
associated software applications are:
Each of these has a University program for supporting schools that wish to teach VHDL and use practical projects
for lab experiments. In addition, these companies offer training support for individuals and instructors to assist in
learning about their software and device products. You can contact each of them at the links in the list above.
Introduction
VHDL is an acronym for Very high speed integrated circuit
(VHSIC) Hardware Description Language which is a programming
language that describes a logic circuit by function, data flow behavior, and/or structure. This hardware description
is used to configure a programmable logic device (PLD), such as a field programmable gate array (FPGA), with a custom logic design. The general format of a VHDL program is built around the concept of BLOCKS which are the basic building units of a VHDL design. Within these design blocks a logic circuit of function can be easily described.
A VHDL design begins with an ENTITY block that describes the
interface for the design. The interface defines the input
and output l1ogic signals of the circuit being designed. The
ARCHITECTURE block describes the internal operation of the
design. Within these blocks are numerous other functional
blocks used to build the design elements of the logic
circuit being created.
After the design is created, it can be simulated and
synthesized to check its logical operation. SIMULATION is a
bare bones type of test to see if the basic logic works
according to design and concept. SYNTHESIS allows timing
factors and other influences of actual field programmable
gate array (FPGA) devices to effect the simulation thereby
doing a more thorough type of check before the design is
committed to the FPGA or similar device.
Many software packages used for VHDL design also support
schematic capture which takes a logic schematic or state
diagram and translates it into VHDL code. This, in turn,
makes the design process a lot easier. However, to fine tune
any design, it helps to be familiar with the actual VHDL
code.
Data Types
VHDL is a very strongly typed language. It does not allow a
lot of intermixing of data types. The idea here is that
since you are describing a piece of hardware, you need to
keep things like signals and numbers separate. We shall
start by looking at the different types of data that can be
used with VHDL which include bits, buses, boolean, strings,
real and integer number types, physical, and user defined
enumerated types.
Defining Signals
There are two data types used for defining interfacing and
interconnecting signals - bits and bit_vectors. The bit type
defines a single binary bit type of signal like RESET or
ENABLE. It is used anytime you need to define a single
control or data line. For multiple bus signals, such as data
or address buses, an array called a bit_vector is used.
Bit_vectors require a range of bits to be defined and has
the syntax: bit_vector(range)
The range for a bit_vector is defined from the least
significant bit (LSB) to the most significant bit (MSB) and
can be set to go from one to the other in ascending or
descending order by using: LSB to MSB or MSB downto LSB. Here are some examples of bit_vector forms:
addressbus(0 to 7);
databus(15 downto 0);
The first defines an 8-bit address bus from addressbus(0) to
addressbus(7). The second, a data bus from databus(15)
downto databus(0).
The IEEE STD_LOGIC_1164 standard includes additional
definitions for VHDL data types. For the bit type, the IEEE
type is STD_LOGIC and for a bit_vector it is STD_LOGIC_VECTOR.
The use of the IEEE standard types assures that your VHDL
code will be portable. That is it can be used by any vendors
implementation software. Bit and bit-vector are types which
are not universally accepted and may not be recognized by
some application programs.
The Boolean Type
The boolean type has only two values: TRUE (1) and FALSE (0)
and is usually used to hold the results of a comparison or
the basis for conditional statement results.
Numerical Types
Number types that are usable in VHDL code are INTEGERS and
REALS. Integers are signed numbers and reals are used for
floating point values. The range of values for both number
types is somewhat dependent on the software application
being used.
Subtyping
VHDL provides a method to create a version of an existing
type with a specified range of values by using the SUBTYPE
declaration. A typical example of the use and syntax of this
operation is:
subtype SHORTINT is integer range 0 to 255;
which creates an integer type, SHORTINT with a specified range of values from 0 to 255. This is NOT a new or
enumerated (user) type which we shall describe next, but rather a modified existing type.
Enumerated or User Data Type
An enumerated data type provides a means for creating and defining user types. They are declared using the TYPE operator with a syntax of:
TYPE type_name (type values);
Once the data type has been declared, then it can be used in
a variable declaration (discussed later). For example, here
is a declaration for a data type called MONTHS:
TYPE MONTHS (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP,
OCT, NOV, DEC);
A variable declared to be of type MONTHS can have anyone of
the twelve values indicated in the parenthesis.
Other Data Types
VHDL specifications include additional data types that are
used in the behavioral description of a circuit design.
These types are:
This has been a summary of the data types used by VHDL. As we progress, we will see how most are implemented in VHDL
design code.
ENTITY BLOCK
An entity block is the beginning building block of a VHDL
design. Each design has only one entity block which describes the interface signals in to a
nd out of the design
unit. The syntax for an entity declaration is:
entity entity_name is
port (signal_name,signal_name : mode type;
signal_name,signal_name : mode type);
end entity_name;
An entity block starts with the reserve word entity followed
by the entity_name. Names and identifiers can contain
letters, numbers, and the under score character, but must
begin with an alpha character. Next is the reserved word is
and then the port declarations. The indenting shown in the
entity block syntax, is used for documentation purposes only
and is not required since VHDL is insensitive to white
spaces.
A single PORT declaration is used to declare the interface
signals for the entity and to assign MODE and data TYPE to
them. If more than one signal of the same type is declared,
each identifier name is separated by a comma. Identifiers
are followed by a colon (:), mode and data type selections.
In general, there are five types of modes, but only three
are frequently used. These three will be addressed here.
They are in, out, and inout setting the signal flow direction for the ports as input, output, or bidirectional. Signal declarations of different mode or type are listed
individually and separated by semicolons (;). The last
signal declaration in a port statement and the port
statement itself are terminated by a semicolon on the
outside of the port's closing parenthesis.
In VHDL, ALL STATEMENTS are terminated by a semicolon.
The entity declaration is completed by using an end operator
and the entity_name. Optionally, you can also use an end
entity statement. Here is an example of an entity
declaration for a set/reset (SR) latch:
entity latch is
port (s,r : in std_logic;
q,nq : out std_logic);
end latch;
The set/reset latch has input control bits s and r which
are defined as single input bits and output bits q and nq.
Notice that the declaration does not define the operation
yet, just the interfacing input and output logic signals of
the design. A design circuit's operation will be defined in
the architecture block.
You can define a literal constant to be used within an
entity with the generic declaration, which is placed before
the port declaration within the entity block. Generic
literals than can be used in port and other declarations.
This makes it easier to modify or update designs. For
instance if you declare a number of bit_vector bus signals,
each eight bits in length, and at some future time you want
to change them all to 16-bits, you would have to change each
of the bit_vector range. However, by using a generic to
define the range value, all you have to do is change the
generic's value and the change will be reflected in each of
the bit_vectors defined by that generic. The syntax to
define a generic is:
generic (name : type := value);
The reserved word generic defines the declaration statement.
This is followed by an identifier name for the generic and a
colon. Next is the data type and a literal assignment value
for the identifier. := is the assignment operator that
allows a literal value to be assigned to the generic
identifier name. This operator is used for other assignment
functions as we will see later.
For example, here is the code to define a bus width size
using a generic literal.
entity my processor is
generic (busWidth : integer := 7);
Presently, busWidth has the literal value of 7. This makes
the documentation more descriptive for a vector type in a
port declaration:
port( data_bus : in std_logic_vector (busWidth downto 0);
q-out : out std_logic_vector (busWidth downto 0));
In this example, data_bus and q_out have a width of eight
(8) bits ( 7 down to 0). When the design is updated to a
larger bus size of sixteen (16) bits, the only change is to
the literal assignment in the generic declaration from 7 to
15.
ARCHITECTURE BLOCK
The architecture block defines how the entity operates. This
may be described in many ways, two of which are most
prevalent: STRUCTURE and DATA FLOW or BEHAVIOR formats. The BEHAVIOR approach describes the actual logic behavior of the
circuit. This is generally in the form of a Boolean expression or process. The STRUCTURE approach defines how
the entity is structured - what logic devices make up the circuit or design. The general syntax for the architecture
block is:
architecture arch_name of entity_name is
declarations;
begin
statements defining operation;
end arch_name;
For our first example, we will use the set/reset NOR latch
of figure 1. In VHDL code listings, -- (double dash)
indicates a comment line used for documentation and ignored
by the compiler.

library ieee;
use ieee.std_logic_1164.all;
--
-- entity block
--
entity latch is
--
-- interface signal declarations
--
port (s,r : in std_logic;
q,nq : out std_logic);
end latch;
--
-- architecture block
--
architecture flipflop of latch is
begin
--
-- assignment statements
--
q <= r nor nq;
nq <= s nor q;
end flipflop;
The first two lines imports the IEEE standard logic library
std_logic_1164 which contains predefined logic functions and
data types such as std_logic and std_logic_vector. The use
statement determines which portions of a library file to
use. In this example we are selecting all of the items in
the 1164 library. The next block is the entity block which
declares the latch's interface inputs, r and s and outputs q
and nq. This is followed by the architecture block which
begins by identifying itself with the name flipflop as a
description of entity latch.
Within the architecture block's body (designated by the
begin reserved word) are two assignment statements. Signal
assignment statements follow the general syntax of:
signal_identifier_name <= expression;
The <= symbol is the assignment operator for assigning a
value to a signal. This differs from the := assignment operator used to assign an initial literal value to generic
identifier used earlier.
In our latch example, the state of the signal q is assigned
the logic result of the nor function using input signals r
and nq. The nor operator is defined in the IEEE
std_logic_1164 library as a standard VHDL function to
perform the nor logic operation. Through the use of Boolean
expressions, the operation of the NOR latch's behavior is
described and translated by a VHDL compiler into the
hardware function appearing in figure 1.
Time Delays
Dataflow or behavioral architectures can also include timing
influences by adding propagation delay values using the
after operator and a time physical data type. An example of
adding a one nanosecond inertial delay to our nor gate
operation appears as:
q <= r nor nq after 1ns;

This would cause output q to go high 1 ns after the
application of the r input. The inertial form requires that
r stay active, as hold time, for a minimum time equal to the
delay time (1 ns in this example). If you want to allow q to
change for r pulses that are shorter than 1 ns, you need to
use the transport delay form which requires the transport
operator to be included preceding the logic statement:
q <= transport r nor nq after 1ns;

Again, the difference between the two time delays is that
without the transport operator, the input has to be present
for the entire delay time while with the transport operator,
the input does not need to be applied for a period equal to
the full delay time period.
Using Vector Types in the Architectural Block
A bit_vector or std_logic_vector type is an array of bits.
The range designates the size of the array and the index
values to be used by the array. Elements of the array are
accessed by using the array name and an index value in the
form of: array_name(index). The best way to see how values
are assigned to an array is to do an example. This is a
multiplexor entity:
library ieee;
use ieee.std_logic_1164.all;
entity demux is
port ( e : in std_logic_vector (3 downto 0);
s : in std_logic_vector (1 downto 0);
d : out std_logic_vector (3 downto 0));
end demux;
architecture rtl of demux is
signal t : std_logic_vector (3 downto 0);
begin
t(3) <= s(1) and s(0);
t(2) <= s(1) and not s(0);
t(1) <= not s(1) and s(0);
t(0) <= not s(1) and not s(0);
d <= e and t;
end rtl;
Before we look at this example line by line, we need an
introduction to a new declaration, SIGNAL. This declaration
is used to define an internal signal for our design. In the
entity block we defined interfacing or external signals that
take information in and return data out. Internal signals
are those used to perform some internal connections or
function between logic entities. A signal declaration has
the syntax:
signal signal_identifier : type;
It is similar to a port signal declaration except for the
lack of a mode indication.
In the demux example, the entity has three array
declarations, two are 4-bits (e and d) and one is 2-bits
(s). Within the architecture block, a local signal is
declared as a 4-bit array (t). The values for t are assigned
in descending order directed by the four state combinations
of s(1) and s(0). Notice how each element is accessed using
the array name and an index value. t(3) is assigned the
results of anding s(1) and s(0). This is a single bit
manipulation and assignment of one bit from each array, bits
t(3), s(1), and s(0). The last line shows how array values
can be assigned for the entire array at one time. The
crucial requirement is that all arrays in the assignment
statement have the same size. If that is the case, than each
element of each array is acted upon individually. ie:
d(3) <= e(3) and t(3)
etc.
Since vectors can be assigned using to as well as downto,
care must be taken in the assignment. If, in the previous
example, d was declared as d : out std_logic_vector( 0 to
3); than the assignment d <= e and t; would assign to d(0)
the result of e(3) and t(3) which may not be what you
intended.
PROCESS
Statements within architecture blocks, to this point, are
executed concurrently - that is at the same time. Also,
there is no way to synchronize their execution with clocking
or any other kind of signals. To incorporate sequential
statement execution and some manner of synchronization, we
need to use a PROCESS block whose general syntax form is:
process_name : process (sensitivity list)
variable variable_names : variable_type;
begin
statements;
end process;
Process statements are placed in the architecture block of
your design. The process_name and variable declarations are
optional. Process names are handy if your design contains
more than one process. Variable declarations are used to
define a variable local to and used by the process. Variable
declarations are added in the declaration area preceding the
body of the process block. In contrast to a signal, variable
declarations define memory locations, identified by variable
identifier names, used to store results of expressions.
Signals, by their nature, cannot be used to perform
arithmetic manipulations such as incrementing or
decrementing their value while variables can be operated on
mathematically. The variable assignment operator is := which
is the same one used for assigning initial literal values.
The syntax for a variable assignment is:
variable_identifier := expression;
To evaluate expressions used in a variable declaration or
process block, you must become familiar with the operators
used by VHDL. Many of them are not strangers to anyone who
has any kind of programming experience. In order of their
precedence, they are:
Now an example of a variable assignment:
cnt := cnt + 1;
As with any other language, the expression on the right is
evaluated first. In this case one is added to the variable
cnt. The results are than stored back into the cnt variable
indicated on the left side of the assignment statement. This
one simply increments cnt by 1. To set this variable
statement into a process block, the code would look like:
count : process(x)
variable cnt : integer := -1;
begin
cnt := cnt + 1;
end process;
The first line of the process syntax is its declaration and contains an optional parameter list, known as the sensitivity list. A process executes once at the beginning of a simulation and any time that an event occurs on an item in the sensitivity list. An EVENT is any change of state of a signal. A change of state on signal x will cause this process to execute once.
The next line is a variable declaration that is similar to a port (signal) declaration. Since it is a variable and not a port, there is no mode selection. Also, variables can be assigned an initial value using an assignment operator as shown in the example. We want cnt to start at 0, but since the process executes once upon starting simulation (without an event occurring on x), we need to initialize cnt to -1. The initial execution of the process due to the start of a simulation will set cnt to 0 by incrementing it once. After that, each time an event occurs on x, cnt will be incremented once, thus keeping track of how many times x changes state. The statements to be executed by the process body follow the begin reserved word. Finally, the process declaration is completed using an end process statement.
Declarations within the process block and preceding the
process body are executed only once - when simulation is
initiated. Thereafter, when the process is run due to an
event on one of the signals on the sensitivity list, only
the body of the process is executed. This prevents variables
from being re-initialized each time the process is run.
All statements in a process execute sequentially. Here are a
couple of examples of process statements with an analysis of
each:
process ( Y )
variable X, Z : std_logic;
begin
X := Y;
Z := not X;
end process;
This is a fairly easy appearing example, but let's take some
time exploring what happens to make sure you fully grasp the
difference between concurrent and sequential operation. Y is
included in the sensitivity list, so it must have been
declared in the design before the process statement.
Variables X and Z are declared in the process block forcing
these variables to be local to the process and not
accessible outside of it.
To follow what happens when the process is executed, let's
assume some initial values for our three variables:
Initial values for variables can be set in the variable
declaration statements using the := assignment operator in
this manner:
variable X : std_logic := 1;
variable Z : std_logic := 0;
Of course, signal Y would have to be initialized before the
process statement to give it a beginning state. In this
case, you would probably use an assignment statement:
Y <= '1';
Since Y has been defined as an interface signal in an
entity, the <= assignment operator is required here.
Assigning a literal logic state, 1 or 0, to a signal
requires a single quote around the 1 or 0. This causes the
software to convert the ASCII 1 or 0 to a logic state and
assign it to the signal. Assigning a string of logic bit
literals to a vector requires double quotes so that the
ASCII string can be converted to logic states for each bit
of the vector. Numerical literals will not use the quotes
around it.
The sample states were not selected as randomly as you might
think. I chose them to illustrate the point of sequential
operation within the process. When Y changes to a 0 through
some outside influence, an event occurs and the process is
initiated. If the statements within the process were
executed concurrently, they would use the initial values to
produce results for all outputs. The change in Y from 1 to 0
causes X to change to a 0 because of the statement X := Y;
Because X had a value of 1 initially, this value is used for
the second statement in concurrent execution. This forces Z
to become 1 from the statement Z := not X;.
However, the statements in the process are executed
sequentially rather than concurrently. What actually occurs
in the process is X becomes 0 when Y changes to 0 as it did
for a concurrent execution. However, this time, Z would
become 1 since the second statement in a sequential
execution would use the new value of X instead of X's
initial value.
Now to a more practical example use of a process which will
also include a method to prevent statements within the
process body from executing when simulation is first begun
and an event has not yet occurred:
library ieee;
use ieee.std_logic_1164.all;
entity DFF is
-- Signals are initialized to 0 by default.
-- To make QN a 1, it has to be initialized
port ( D, CLK : in std_logic;
Q : out std_logic;
QN : out std_logic := '1');
end DFF;
architecture data_flip of DFF is
begin
process ( CLK )
begin
if (CLK = '1' and CLK'event ) then
Q <= D after 10ns;
QN <= not D after 10ns;
end if;
end process;
end data_flip;
There is a lot going on in this short design, so let's
examine it carefully. The only wrinkle in the entity block
is covered by the comment lines which are always preceded by
a double dash (--). Identifiers of all kinds are usually
initialized by most compilers when they are declared, to 0
or null. To set QN to the opposite state of Q initially, we
had to assign it an initially value of 1 by using := '1'
following its port declaration. The rest of the entity block
is straight forward.
In the architecture block we did not require any local
variables or signals so none are declared. The process block
contains one signal in the sensitivity list, CLK. The only
statement in the process body is an if..then..else
statement. The if..then..else statement which is explored in
more detail later, has a standard format of:
if condition then
statements;
else
statements;
end if;
The else block is optional and is used when there are
statements to be executed when the conditional test returns
a false result. The then statements are executed when the
condition rings true.
The if..then..else statement in the example has two
conditions and both have to be met to execute the statements
within the then block. The first condition requires the
state of CLK to be high. The and operator in the condition
field forces a second condition to also be true. This
condition is CLK'event which says that an event must have
occurred on CLK to be true. What this format really accesses
is a property of the process object called an event. If the
event occurred, CLK'event returns true. If no event
occurred, it returns a false value. The inclusion of this
condition eliminates the execution of the statements within
the if block when simulation first begins since the lack of
a CLK event causes CLK'event to be false. The only time the
if condition will be satisfied is when an event on CLK
occurred. Additionally, CLK has to be high, so this
combination causes the then statements to be executed only
on a positive transition (edge) of the CLK signal.
By now, you should notice some significant difference in
declaring and initializing integers and signals. The :=
operator is used to assign initial values in a variable
statement. Notice that for signals, single quotes are
required around the initial value ('0') while none are used
for an integer (0). This is because signal values are logic
states and integer values are numerical. Numerical values do
not require quotes.
Also notice the difference when integer variables are
assigned a value from an expression compared to a signal
assignment. In a previous example we used Y <= A and B; to
assign to Y the results of A and B. In this most recent
example, we did a arithmetic operation on an integer value
and assigned the results to it: cnt := cnt + 1; It is very
easy to use the incorrect assignment symbol (:= or <=) since
they look so similar, so take care!
CONDITIONAL STATEMENTS
if..then..else
The primary conditional test function is the if..then..else
construct that works the same as it does in any programming
language. The syntax for this function is:
if conditional_test then
statements;
else
statements;
end if;
The statements following then are executed if the condition
is true. The else block is optional and used only if there
is an alternate process required to be done if the
conditional result is false. If statements can be nested
using an elsif block. In that case, the syntax is:
if conditional test then
statements;
elsif conditional test then
statements;
else
statements;
end if;
If a compound conditional is desired then parenthesis may be
used to encase the entire conditional test section. Here is
an example if..then..else application:
count : process(x)
variable cnt : integer := 0;
begin
if (x = '1' and x'last_value = '0') then
cnt := cnt + 1;
else
cnt := cnt - 1;
end if;
This code section is processed whenever an event occurs on
x. The if statement checks to see if x is now 1 and was
previously a 0 (x'last_value = '0'). If this is true cnt is
incremented once. If this is false, which means that x
changed from a 1 to a 0, then cnt is decremented once. The
final result will tell the user what the last change was (a
cnt value of 1 indicates a change from 0 to 1 and a 0 cnt
value indicates the last change was from 1 to 0).
Example:
Y <= A and B WHEN S = '0'
ELSE A or B;
Identifier is assigned the expression for the WHEN condition that is true.
Example:
print1 <=
user1 WHEN (en = '1' and sel = '0') ELSE
user2 WHEN (en = '1' and sel = '1') ELSE
user3 WHEN OTHERS;
:= may be used instead of <=
Conditional test is done on all values concurrently. Assignment is made for true WHEN condition.
Example:
LOOPS
The For Loop
The for loop is used to repeat the execution of a section of
code for a given number of times. The general syntax for a for loop is:
for variable in range loop
statements;
end loop;
The range has the same format as the range used in a
bit_vector assignment except, in a for statement, it also
defines the direction for the value of the variable for each
iteration of the loop (to increments and downto decrements).
Here is an example:
signal x : bit_vector (7 downto 0);
.
.
process(x)
variable p : std_logic;
begin
p := '0';
for i in 7 downto 0 loop
p := p xor x(i);
end loop;
end process;
The first line is a signal assignment not enclosed in an
entity. A local variable (p) is assigned within the process
and it is initialized to 0 when the process is begun in
response to an event on x. The eight bits of x are
exclusively ORed with each other to produce the even parity
for the word in x. When entering the loop, i is initialized
to the first value in the range (7). In the first iteration
of the loop, x(7) is XORed with p (0), the result is returned
to p, and i is decremented. In the second iteration, x(6) is
XORed with p again and the result, once more, is returned to
p and i is decremented again. This sequence repeats until i
= 0. The last iteration, XORs x(0) with the accumulated
result in p and exits the loop. When the loop is finished, p
will hold the even parity state for word x.
WHILE LOOPS
STRUCTURE DESIGN
Recall that the architecture block defines how the entity
operates and can be described in many ways, two of which are
most prevalent: structural and behavioral (data flow). We
have already looked at the behavior type of architecture.
This section explores the structural approach which defines
how the entity is constructed - what logic devices make up
the design. The general format for the structural
architecture is:
architecture architecture_name of entity_name is
internal interconnecting signal declarations;
component (object) declarations;
begin
iterations (copies) of components;
end architecture_name;
COMPONENTS
A component is created from any previously designed entity.
It can be declared on the same page as the current design or
imported from a library. A component defines the design
(object) which can be instantiated or copied. As such, there
are some definite procedures to follow when using components. The general syntax for a component declaration
is:
component entity_Name
port (names: mode type);
end component;
Components are related to the entity through the use of the
same name and port list. Port signal declarations must be in
the same order as the entity port declarations and must have
the same name, mode and type - after all, a component is
going to be nothing more than a reference for a copy of an
entity design.
The content of a component block must match the content of
the source entity block exactly. Once the component is
declared, it may be instantiated (copied) and reused any
number of times within the design by the use of this code:
part_name : component_name
port map (signals in exact order);
For an example, we will use the following NOR latch which is
similar to one in figure 1:

And here is the entity and architecture blocks of the
structure design:
library IEEE;
use IEEE.std_logic_1164.all;
-- NOR gate entity design
entity nor_gate is
port (a,b : in std_logic;
c : out std_logic);
end nor_gate;
architecture my_nor of nor_gate is
begin
c <= a nor b;
end my_nor;
-- begin latch design
entity latch is
port (s,r : in std_logic;
q,nq : out std_logic);
end latch;
architecture flipflop of latch is
-- NOR gate component declaration
component nor_gate
port (a,b : in std_logic; c out std_logic);
end component;
begin
-- instantiation of two NOR gates
n1 : nor_gate
port map (r, nq, q);
n2 : nor_gate
port map (s, q, nq);
end flipflop;
A number of concepts are illustrated by this example, so
let's explore each of them. First, we have the component
declaration placed in the architecture block preceding the
architecture body. It starts with the reserve word component
followed by the component's name, in this case - nor_gate.
As with an entity, signals for the component are declared
using the port function. In this example we have two inputs,
a and b and one output, c. An end component statement
completes the declaration. This creates a component OBJECT.
Notice that the nor_gate component and its corresponding
entity declarations are identical except for the component
and entity reserved words. The importance of this, is that
we will declare two instances of this object, each of which
will inherit the properties of the nor_gate component
object.
The first instance is n1 and notice that its declaration is
placed in the body of the architecture block. This means
that the architecture wants to use this instance for the
latch. In order to inherit nor_gate properties, the signals
used by the instance must be MAPPED from the nor_gate
object. This is accomplished using the port map statement.
The signals mapped must be in the same order as the
component object. In this case r and nq inherit the input
function from a and b of the nor_gate object. q gets the
output function from c in the nor_gate component.
In this simple example, there are no extra interconnecting
signals. All connections are established using signals of
the NOR gate components. Here is a little different design
that incorporates interconnecting signals between
components.
SIGNALS

Before we code this one, let's take a close look at the
circuit. There are four parts: two AND, one OR, and one
INVERTER gate. Many of the component signals are connected
to external signals:
There are also internal signal connections between
components which have the following labels:
In a structural design, the internal signals are declared
using the SIGNAL declaration which has the syntax:
SIGNAL : TYPE;
Notice the lack of a mode designation in the signal
declaration. This is because, these are interconnecting
signals between components. This means that they are
connecting an output from one component to the input of
another. That alone would make it difficult to declare a
mode for them. Secondly, they are internal interconnecting
signals. Direction is determined by existing port interface
declarations of the entities of the components being
connected.
For a structural design, you are only stating how parts are
connected together. The design depends on previously defined
behavior of the individual parts which determine how the
design will work. With all of this, let's code the design
and then take a good look at it.
library IEEE;
use IEEE.std_logic_1164.all;
-- Define three logic components
-- AND Gate
entity my_and is
port ( A, B : in std_logic;
Y : out std_logic );
end my_and;
architecture and_gate of my_and is
begin
Y <= A and B;
end and_gate;
-- OR gate
entity my_or is
port ( A, B : in std_logic;
Y : out std_logic );
end my_or;
architecture or_gate of my_or is
begin
Y <= A or B;
end or_gate;
-- Inverter gate
entity my_inv is
port ( A : in std_logic;
Y : out std_logic );
end my_inv;
architecture inv_gate of my_inv is
begin
Y <= not A;
end inv_gate;
-- Start 2-bit multiplexor
entity my_mux is
port ( Sel, Ain, Bin : in std_logic;
Dout : std_logic );
end my_mux;
architecture mux_ckt of my_mux is
-- signal declarations
signal S0, S1, NotS : std_logic;
-- component declarations
component my_and is
port ( A, B : in std_logic;
Y : out std_logic );
end component;
component my_or is
port ( A, B : in std_logic;
Y : out std_logic );
end component;
component my_inv is
port ( A : in std_logic;
Y : out std_logic );
end component;
-- structural design body
begin
X1 : my_and port map ( Sel, Ain, S1 );
X2 : my_and port map ( Bin, NotS, S0 );
X3 : my_inv port map ( Sel, NotS );
X4 : my_or port map ( S1, S0, Dout );
end mux_ckt;
From the top! This design begins by defining three logic
gates, my_and, my_or, and my_inv for AND, OR, and INVERTER
gates. Keep in mind, that all three of these gates could
also have been imported from a library file. More on
libraries in a later section. For now, this design includes
the three entity designs for the logic gates. Each
architecture of the gate designs is in behavior or data flow
form. That is the behavior of the gate is described for
each. In the architecture block there are several
declarations made before the beginning of the body portion.
The first declares the internal signals used in the design.
Since VHDL is hierarchical in nature, these signal
declarations have to made before the component
instantiations that use them. Again, notice the lack of mode
statements in the signal declarations.
The remaining declarations define the components used by the
architecture. Here you will see copies of the entity
declarations, but instead of the reserved word entity, you
use component. After their declarations starts the body of
the architecture block designated by the begin reserve word.
Look at the simplicity of this part of the design. Its only
a list of components and how signals are connected to them.
X1 and X2 are copies of my_and, X3 of my_inv, and X4 of
my_or. Notice that the port map signals are in the same
order as the component and entity port declarations. Suppose
you do not know the order. There is a way around that known
as DIRECT ASSOCIATION which does require, minimally, that
you know the port signal names and their intended use. Here
is an alternate way of instantiating X1 as my_and:
X1 : my_and
port map (
A => Sel,
Y => S1,
B => Ain );
The order of the port map list no longer matters since each
individual signal relationship is defined individually. In
effect, instantiating components is similar to placing logic
ICs, like the 7400 series, onto a protoboard (components)
and interconnecting them with wires (interconnecting
signals). Logic signals are applied to inputs and outputs
are monitored (interface - entity port signals).
MODULAR DESIGN
Now that my_mux is completed, it can be used as a component
or MODULE in a larger design, say an 8-bit multiplexor. You
must start by treating the my_mux design as a component with
three inputs (Ain, Bin, Sel, and Dout) that could be
represented by this block:

Basically, just continue with the mux design by adding a new
entity:
entity eight_mux is
port ( Ain, Bin : in std_logic_vector (7 downto 0 );
Dout : out std_logic_vector ( 7 downto 0 );
Sel : in std_logic );
end eight_mux;
architecture multiplex of eight_mux is
-- declare my_mux component
component my_mux is port map ( Ain, Bin, Sel : in std_logic;
Dout : out std_logic );
end component;
-- instantiate my_mux component eight times
begin
M7 : my_mux port map ( Ain(7), Bin(7), Sel, Dout(7) );
M6 : my_mux port map ( Ain(6), Bin(6), Sel, Dout(6) );
M5 : my_mux port map ( Ain(5), Bin(5), Sel, Dout(5) );
M4 : my_mux port map ( Ain(4), Bin(4), Sel, Dout(4) );
M3 : my_mux port map ( Ain(3), Bin(3), Sel, Dout(3) );
M2 : my_mux port map ( Ain(2), Bin(2), Sel, Dout(2) );
M1 : my_mux port map ( Ain(1), Bin(1), Sel, Dout(1) );
M0 : my_mux port map ( Ain(0), Bin(0), Sel, Dout(0) );
end eight_mux;
That's it. Notice that the eight bit multiplexor is easier
to design because most of the preliminary work was already
done. You are merely making use of previously defined
components at this point. This is the power of structural
design.
FUNCTIONS
A function in VHDL is similar to functions in most upper
level languages. It is a subprogram that accepts input
parameters and returns a single result. The syntax for a
function declaration is:
function function_name ( formal parameters )
return return_type is
variable declarations;
begin
statements;
return return_variable_name;
end function_name;
Here is an example of a function that computes the parity on
a data array of unspecified length:
function parity ( word : std_logic_vector )
return std_logic is
variable tmp : std_logic;
begin
for i in word'range loop
tmp : = tmp xor word(i);
end loop;
return tmp;
end parity;
When the function is called, a specific variable of
std_logic_vector type is passed to it. A tmp variable of
std_logic type is declared and, by default, is initialized
to zero. The for loop exclusive ORs tmp with each bit of
the word array. The loop repeats from 1 to the number of
bits in the array (word'length). The final value of tmp (the
even parity state of the word passed in) is passed out of the
function via the return statement.
Function Call
Functions are called using a single assignment statement
that has the general form of:
variable <= function_name ( actual parameters );
The actual parameter(s) passed to the function must be of
the same type and length (if a specified array size was
made) as the formal parameter(s) in the function
declaration. The returned value of the function is stored
into the variable on the left side of the expression.
Function call example:
even_parity <= parity( data_bus_in );
This function call accepts the vector array data_bus_in and
calculates its parity, returning the even parity result to
the variable even_parity.
PROCEDURES
A procedure like a function is a subprogram that must first
be declared and then called. Unlike the function, procedures
can pass out numerous results through its parameter list.
Because of this, parameter declarations must include an in
or out mode declaration as well as a data type indication.
The syntax for declaring a procedure is:
procedure procedure_name (formal parameter : mode type,
formal parameter : mode type ) is
variable declaration;
begin
statements;
end procedure_name;
Since there are possible multiple results, procedures are
not called using an assignment statement like a function.
Instead they are called using this format:
procedure_name (actual parameter list);
The parameter list contains the names of the actual
parameters to be passed in and out of the procedure and
while they do not have to have the same identifier names as
those in the declaration, they must follow the exact order
as well as having the same mode and type as the formal
parameters in the procedure declaration. The following
procedure returns two results Z and ZCOMP based on the
values of words A and B and the type of operation to be
performed on them.
type op_code is ( ADD, SUB, MUL, DIV, LT, LE, EQ );
.
.
procedure ARITH_UNIT ( A, B : in integer; op : in op_code; Z
: out integer; ZCOMP : out boolean ) is
begin
case op is
when ADD => Z := A + B;
when SUB => Z := A - B;
when MUL => Z := A * B;
when DIV => Z := A / B;
when LT => ZCOMP := A < B;
when LE => ZCOMP := A <= B;
when EQ => ZCOMP := A = B;
when others => Z := Z;
end case;
end ARITH_UNIT;
Somewhere, preceding the procedure declaration is an
enumerated user type declaration creating a new type op_code
with the values indicated in the parenthesis. The procedure
ARITH_UNIT is declared with three input parameters passed
into the procedure - A and B which are integer types and op
which is an op_code type. Two output parameters, integer Z
and Boolean ZCOMP contain the results passed out by the
procedure. In the body of the procedure, op is checked for a
value using a case statement and dependent on that result,
one of the when assignments is performed. An example
statement that calls this procedure is:
ARITH_UNIT ( word1, word2, operation, result, comp_check );
word1 and word2 are passed in as parameters A and B.
operation enters as parameter op. The results of the
procedure are passed out through parameters Z and ZCOMP to
variables result and comp_check.
In this section, we will create a mod-3 counter using the ALDEC state diagram entry tool.
Libraries are a convenient way to store and retrieve
designs, functions, procedures, and other commonly used
items. You should be familiar with the IEEE STD_LOGIC_1164
LIBRARY by now if you have created a simple design. This
library contains definitions for many of the VHDL IEEE
standard data types and logic functions. However, you do not
need to use this library to create a logic design. VHDL has
a built in library that is automatically accessed without a
library or use statement. This library is specified by IEEE
as VHDL 1076 and contains some of the basic definitions of
VHDL reserve words and operators. For instance, here is a
design for an AND gate that uses only the VHDL 1076 built in
library:
entity and_gate is
This is a complete design that does not require any
additional libraries. It defines an AND function using VHDL
1076 terms only.
To access an existing library, you must declare the location
of the library and which parts of it you want to use. The
syntax for access the entire contents of the
IEEE.STD_LOGIC_1164 is:
LIBRARY IEEE;
The library statement denotes the parent library of files
which contains defined functions, procedures, operators, and
processes. This declaration must precede your design.
The use statement selects which groups of files from the
parent library you want to use in your design. The most
common use call is all to indicate you want access to the
entire contents of the library file.
Each entity in your design must be preceded by the use
clause if that section of the design file is to use the
contents of the 1164 library. When you compile your design,
the requested files from the library are accessed and added
to your design. In turn, at the completion of the compile
function, your design is added to a working library, aptly
referenced to by the reserved word WORK. The contents of
this library are accessible by any design in the current
open project as long as you indicate it use with the
statement:
use work.package_name.all;
PACKAGES are units that are defined at the beginning of your
design to hold commonly used functions, procedures,
constants, etc. We will get into details about packages
shortly. Additionally, designs in the current file can be
accessed from the library that the compile process stores
completed designs into when they are successfully compiled.
For instance if you are working on a project you named
my_gates, than a library with that name has been created and
holds copies of your designs that are in that project. You
can access them for new designs by denoting the project name
as the parent library in the library and use statements:
LIBRARY my_gates;
If this library is in a different folder from your current
design, then you must specify the parent library name in the
library and use statements:
LIBRARY my_designs;
Many VHDL library files have been created to store commonly
used functions and device entities. Among them are three
fairly commonly used files, std.textio for handling input
and output functions, ieee.arith which contains many
mathematical processes, and ieee.std_logic_1164 which is an
IEEE standard for logic devices and symbols. Library files
are included into a listing by using the reserved word use
which defines which parts of the library file are used.
One use of std.textio is to send data to the screen with
write and writeline functions. The textio functions are
usually included in a standard library file that is always
available to the VHDL interpreter, hence there is no need to
declare its parent library in the library and use
statements. Instead, the prefix std is used to access the
standard library file. A write(variableName,data) appends
the data to the variable designated by variableName.
Writeline(output,variableName) directs the contents of the
variable called variableName to the standard output port
which is the monitor screen. Here is an example use of the
write statements:
use std.textio.all;
One requirement is to make sure that s is empty before you
start. The first write statement appends the text Counter
Overflow - to variable s. The second statement takes the
value of variable cnt and appends that to s. The third
statement sends the message to the screen. If the value of
cnt was 33, than the message on the screen would be: Counter
overflow - 33.
PACKAGES
As briefly mentioned earlier, packages are a way of storing
commonly used items in a library file. If the package is to
hold basic items like constants and type declarations then
it is only required to define the package at the beginning
of a design. The package will be added to that design's
library when it is compiled. If you wish to create a library
strictly for holding packages, then do not include any
designs (entity declarations). When it is compiled, the
defined packages will be saved in the created library file.
The syntax for a simple package declaration is:
package package_name is
If the package is declared at the beginning of a file
containing actual circuit designs, then the designs can
access the packages contents using the use statement and
work library:
use work.package_name.all;
Here is an example design of a four bit rotate operation
using a local package:
For functions and procedures, they are packaged by first
declaring the package and then defining the subprogram in a
PACKAGE BODY. Here is an example creation of an eight bit
register procedure which is stored into a library package
called my_8bit_reg:
This can be compiled as is, which would store a package
called my_8bit_reg into the current library, such as
my_gates. It can be accessed by other projects with these
preceding statements:
library my_gates;
Logic designs can have only one entity, but may have
multiple architectures. The last architecture in a design is
the default architecture. As an example, here is a design
for an AND gate with two architectures:
There are two ways to access the 3-input AND gate as a
component. One is to take advantage of its default status as
the last architecture of the my_and design by applying this
use statement after the library statement:
use my_gates.my_and.all;
The other way is to specify the precise architecture you
want to use. This is the only way you can access the the
two-input gate:
use my_gates.my_and.and3; for the three input gate.
use my_gates.my_and.and2; for the two-input gate.
You cannot import both architectures into the same design.
The component declaration is the same for each choice since
it must match the entity declaration exactly. When
instantiating the component, copies must indicate a defined
signal for each port signal in the entity/component statement
even if one is not used (input C in the two-input AND gate)
as illustrated in the following example.
In this design, signals U, V, and M are dummy signals used
to satisfy the entity and component port declarations in the
instantiations for gate1, gate2, and gate3. The final
Boolean expression for this circuit is Q = W and X and Y and Z.
gate1 uses inputs W and X to produce output S. gate2 has
inputs Y and Z and output T. Finally, gate3 uses S and T to
generate circuit output Q.
The set of links below will take you to a number of labs based on VHDL designs. The first three
These labs were written with Active HDL in mind, but can
These next set of labs were designed around Altera's MAX+plusII Software,
Here a list of VHDL syntax:
-----------------------------------------------------------
------------------------------------------------------------
----------------------------------------------------------

State Diagram Entry


reset = '1' and
enable = '1'
tc <= '0';
conditions: reset = '1' and enable = '0'
action: tc <= '0';






LIBRARY FILES
port ( A : in bit_vector ( 1 downto 0 );
Y : out bit);
end and_gate;
architecture and of and_gate is
begin
Y <= '1' when A = "11" else '0';
end and;
USE IEEE.STD_LOGIC_1164.ALL;
use my_gates.all;
use my_designs.my_gates.all;
.
.
write(s,"Counter Overflow - ");
write(s,cnt);
writeline(output,s);
.
.
package_contents;
end package_name;
library ieee;
use ieee.std_logic_1164.all;
--
-- package declaration
--
package my_types is
--
--create sub type for an eight bit vector
--create a constant called clear which is of byte type
--initialized to 0
--
subtype byte is std_logic_vector (7 downto 0);
constant clear : byte := (others => '0');
end my_types;
--
--define library uses for rotate design
--
use ieee.std_logic_1164.all;
use work.my_types.all;
--
--declare rotate entity
--
entity rotate is
port (clk, reset, load : in std_logic;
--
--declare two buses of subtype byte from package my_types
--
data in : byte;
Q : out byte);
end rotate;
--
--architecture of rotate
--
architecture rotate4 of rotate is
--
--rotate process
--
process (clk, reset)
signal Qtemp : byte;
begin
--
--check for reset and set Qtemp to clear constant
--
if reset = '1' then Qtemp <= clear;
--
--check for positive edge trigger of clk
--
elsif (clk = '1' and clk'event) then
--
--check load, set to data input if load = 1
Qtemp <= data when (load = '1') else
--
--do rotate if load = 0
--
Qtemp := Qtemp(byte'length-1 downto 1) and Qtemp(0);
end if;
Q <= Qtemp;
end process;
end rotate4;
--First, the package declaration
--
library ieee;
use ieee.std_logic_1164.all;
package my_8bit_reg is
subtype byte is std_logic_vector (7 downto 0);
constant clear : byte := (others => '0');
procedure reg8 (reset, clk : in std_logic; data_in : byte;
Qout : byte);
end my_8bit_reg;
--
--create package body containing actual procedure
--
package body my_8bit_reg is
procedure reg8 (reset, clk : in std_logic; data_in : byte;
Qout : byte) is
begin
if reset = '1' then Qout <= clear;
elsif clk = '1' and clk'event then
Qout <= data_in;
end if;
end reg8;
end my_8bit_reg;
use my_gates.my_8bit_reg.all;
MULTIPLE ARCHITECTURE DESIGNS
library ieee;
use ieee.std_logic_1164.all;
entity my_and is
port ( A, B, C : in std_logic;
Y : out std_logic );
end my_and;
--
-- architecture 1
--
architecture and2 of my_and is
begin
Y <= A and B;
end and2;
--
-- architecture 2
--
architecture and3 of my_and is
begin
Y <= A and B and C;
end and3;
library ieee;
use ieee.std_logic_1164.all;
library my_gates;
use my_gates.my_and.and2;
entity and4 is
port ( W, X, Y, Z : in std_logic;
Q : out std_logic );
end and4;
architecture four_and of and4 is
signal S, T, U, V, M : std_logic;
component my_and is
port ( A, B, C : in std_logic;
Y : out std_logic );
end component;
begin
gate1 : my_and port map ( X, W, U, S );
gate2 : my_and port map ( Y, Z, V, T );
gate3 : my_and port map ( S, T, M, Q );
end four_and;
Lab Pages
are tutorials for the three leading development packages:
be adopted to any design software:
but can also be adopted to any other design software:
REFERENCE- comment
library ieee;
use ieee.std_logic_1164.all;
entity entityName is
port (signalName : mode type);
end entityName;
signal name : mode type;
signalName <= expression;
signalName <= value when (condition)
else otherValue;
architecture entityName is
assignments;
begin
statements;
end entityName;
processName : process (sensitivity list)
assignments;
begin
statements;
end processName;
if (condition) then
statements;
else
statements;
end if;
if (condition) then
statements;
elsif (condition)
statements;
else
statements;
end if;
for loop_variable in range loop
statements;
end loop;
range notations:
value downto value
value to value
EXAMPLE FILES
8-bit Comparator
-- 8-Bit Comparator
library ieee;
use ieee.std_logic_1164.all;
entity compare is
port (A,B : in bit_vector(0 to 7);
EQU : out bit );
end compare;
architecture compare1 of compare is
begin
EQ <= '1' when (A = B) else '0';
end compare1;
8-bit shift register
-- 8-Bit Shift Register
library ieee;
use ieee.std_logic_1164.all;
entity rotate is
port (CLK, RST, LOAD : in bit;
Data : in bit_vector(0 to 7);
Q : out bit_vector(0 to 7));
end rotate;
architecture rotate1 of rotate is
begin
reg:process(RST, CLK)
variable Qreg : bit_vector(o to 7);
begin
if(RST = '1') then
Qreg := "00000000";
elsif(CLK = '1' and CLK'event) then
if(LOAD = '1') then Qreg := Data;
. else
Qreg := Qreg(1 to 7) & Qreg(0);
end if;
end if;
Q <= Qreg;
end process;
end rotate1;
Second 8-bit shift register
-- 8-Bit Shift Register Two
library ieee;
use ieee.std_logic_1164.all;
entity rotate is
port (CLK, RST, LOAD : in bit;
Data : in std_logic_vector(0 to 7);
Q : out std_logic_vector(0 to 7));
end rotate;
procedure dff(signal Rst, Clk : in bit;
signal D : in std_logic_vector(0 to 7);
signal Q : out std_logic_vector(0 to 7)) is
begin
if(Rst = '1') then
Q <= "00000000";
elsif(Clk = '1' and Clk'event) then
Q <= D;
end if;
end diff;
architecture rotate2 of rotate is
signal D, Qreg : std_logic_vector(0 to 7);
begin
D <= Data when(LOAD = '1')
else
Qreg(1 to 7) & Qreg(0);
diff(Rst, Clk, D, Qreg);
Q <= Qreg;
end rotate2;
CASE Example: 4 to 1 Multiplexer
library IEEE;
use IEEE.std_logic_1164.all;
-- declare mux entity
entity my_mux is
port ( A, B, C, D : in std_logic;
Sel : in std_logic_vector ( 0 to 1 );
Y : out std_logic );
end my_mux;
-- architecture block
architecture mux_4 of my_mux is
constant mux_delay : time := 15ns;
begin
mux_proc : process ( A, B, C, D, Sel )
variable Y_temp : std_logic;
begin
case Sel is
when "00" => Y_temp := A;
when "01" => Y_temp := B;
when "10" => Y_temp := C;
when "11" => Y_temp := D;
when others => Y_temp := 'X';
end case;
Y <= Y_temp after mux_delay;
end process mux_proc;
end mux_4;
VHDL Questions
Data Types
Entity
Architecture Block
Process
Conditional Statements
Loops
Structure Design
Signals
Modular Design
Functions
Procedures
Library Files
Multiple Architecture Design
![]() Return to site map. |
![]() Return to home page. |