VHDL 101 - Everything You Need to Know to Get Started
Last Update: 2017-12-08
Five Sentence Abstract:
The book is divided into two main parts, the first of which is a review (if you read FPGA 101 first) of coding styles (behavioral, structural, register transfer level) and the design process including synthesis, simulation, and implementation, while the second part is further divided into a 3 pass approach to VHDL, going deeper with each iteration. Part two starts off with the basics including entities and architectures, signals, data types, operators, concurrent statements which give you a solid foundation to build on. The next section builds on this foundation with the introduction of processes, variables, sequential statements, and some of the limitations of simulation versus synthesis. The last, and most detailed, iteration introduces libraries, generics, the generate statement, loops, functions, procedures, and packages. It concludes with a look at a few common libraries and a nice quick reference appendix that is helpful when first learning a language.
A welcome follow up to FPGA 101. It includes
just the right amount of simple code snippets that are explained line by line.
Also included are a few larger, more realistic examples (UART) that are equally
Completing Eduvance the VHDL
tutorial I watched as I read FPGA 101 proved to be a nice supplement for
Here is another tutorial
series I worked through as I read this book. This is a basic beginner
tutorial that eases you into VHDL. It uses a homebrew development board, but I
did everything in simulation since I don't have any board yet.
The more advanced Bruce Land/Cornell FPGA/Verilog
course, ECE5760 DE2/115 lectures 2011, available on youtube was also more
approachable as I read this book. It is quite a bit more advanced and uses
Verilog instead of VHDL.
Table of Contents
01 - Introduction and Background
02 - Overview of the Process of Implementing an FPGA Design
03 - Loop 1 - Going with the Flow
04 - Loop 2 - Going Deeper
05 - Loop 3
VHDL, as with so many other languages, is difficult
to teach as each part of the language requires the understanding of some other
part of the language to make sense. This makes the traditional "linear"
approach very difficult as it requires the reader to read the book from start
to finish in order to have a sufficient grasp of the language to become
useful. This book is experimenting with a different approach – it covers
various aspects of the material multiple times! Each "loop" builds upon the
previous and introduces new material along with practical application.
// Read from start to finish to understand? You don't
2 I use "he" instead of the awkward "he/she" or the more encompassing
"he/she/he–she/she–he/it" This is not out of sexism, but out of long tradition,
in virtually every language, of using the third person masculine for when the
gender of the reader is unknown (as opposed to indeterminate).
// Another stumble on a rocky start. PERCEIVED sexism is
ruining just about everything. This kind of display, even in its attempt at
humor, simply gives more credence to the insanity of "gender inclusion". If you
are reading this, please, simply do not mention this nonsense and it will go
away. To keep picking at it like a scab on makes the scar worse.
- Way back, in the BEFORE TIME, say around 1981, the US Department of Defense
began writing the specifications for the Very-High-Speed Integrated Circuit
(VHSIC) program. The intent was to be able to accurately and thoroughly
describe the behavior of circuits for documentation, simulation, and (later)
synthesis purposes. This initiative was known as VHDL. July of 1983 saw
Intermetics, IBM, and Texas Instruments begin developing the first version of
VHDL, which was finally released in August of 1985. Just to flesh out the
timeline, Xilinx invented the first FPGA in
1984, began shipping parts in 1985, and was supporting VHDL shortly
thereafter using a third-party synthesis tool.
- Structural VHDL or structural coding is a very
low-level style of coding. This practice involves instantiating, or calling out
explicitly, specific instances of primitives or previously defined modules and
wiring them together.
[structural VHDL is typically used] at the top
level of a design where large lower-level modules are stitched together, when a
specialized piece of Intellectual Property (IP) is provided often as a "black
box"4, or when specific architectural features such as block memory,
high-performance communication cores, or other specialized silicon resources,
RTL stands for Register Transfer Level and refers to a coding style that
defines how signals flow between hardware registers and the logical operations
that occur between the registers. This is one step more abstracted than
structural coding as the independent logic elements don't need to be
instantiated but can rather be inferred5 from equations.
The next level more abstracted is called "Behavioral Coding" This adds an
additional layer of abstraction in that software-type constructs such as "for
loops" "if statements" and "case statements"
1."Divide and Conquer" - break the design into manageable pieces
a. Divide by functionality
b. Complex modules can be further broken into smaller pieces7 until basic
functionality is reached
c. Write a single sentence describing what each module at each level of
hierarchy does. If you can't do this, you don’t have a clear enough idea
of what the module should do. Think about it some more
Draw a top-level block diagram for each hierarchical level in the design
a. Draw lower levels of the design hierarchy for more complex blocks
List the signals which move from module to module
a. For the same level of hierarchy
b. Through vertical levels of hierarchy
c. Don't just list signal names, but include types and units8
d. Choose meaningful signal names – be descriptive, complete, and unambiguous
Don't start coding until the above steps are complete
- For those readers with a software background, think of lower-level modules as
subroutines. The "instantiation" is equivalent to the subroutine "call" and
the signals listed in the "instantiation" are equivalent to the subroutine's
- VHDL is a file-oriented language and, usually, there is just one entity and
one architecture3 described within a file.
try a name such as upperQuadrantTap12 (which uses a Java style naming
convention) or upper_quadrant_tap_12 (which is more of
a C/C++ naming convention and more commonly used in industry).
The C/C++ naming conventions are typically used with
VHDL as the mix of upper and lowercases is generally lost as VHDL converts
everything to lowercase during processing.
- One of many signal-naming conventions:
- "Work" library that represents the location to which all compiled code is
located5 and the "STANDARD" library which contains basic data types.
- The entity acts as the interface between this module and any other modules or
the outside world. It contains the name of the module and a list of signals
with a description of what those signals look like.
Every module must have one and only one entity.
The architecture is where the description of the circuit occurs. This is
further broken down into two regions. The "what is to be used" (definitions)
area resides between the "architecture" statement and the "begin" statement.
This portion defines the various signals, constants, functions, procedures, and
other modules used in this module.
- There may be more than one architecture per entity, but this requires a
configuration statement to resolve. The way that the configuration statement
resolves this is through the name of the architecture. Only one architecture
per entity can be "active" at a time.
- Signals are the primary method for moving information within an FPGA. Simply
put, signals become "wires" when synthesized. Signals can be of any legal data
type and are valid only within the architecture in which it is declared. So, if
you have two VHDL files and both define a signal named "data_arrived" within
the architecture, then both versions of data_arrived are completely independent
signal input_from_laser_gyro: std_logic_vector(15 downto 0) := (others=>’0’);
- When naming a signal, use _n to show that a signal or variable is active
- Make every effort to code resets within the FPGA as active high since this
generates the most efficient FPGA code.
although VHDL is a case insensitive language, when enumerated types are
enclosed by single quotes, the case does become
As a designer, you will most likely use your own enumerated types for naming
various states within a state machine.
Character - one of 256 characters defined in the ISO 8859-1 character set.
Characters, like Booleans, are generally used more often in test benches than
in synthesizable designs.
Boolean - comprised of the values "true" and "false" Although synthesizable,
Booleans more commonly see application in test benches. Typically Booleans are
used to temporarily store the results of complex comparisons.
integers are usually given a subrange. Limiting the range of an integer
predefined floating type is that of the REAL.
Floating types are not intrinsically synthesizable, that is, they are limited
to simulation only. That said, the IEEE does have a proposed synthesizable
floating-point standard which does work, however, the implementation
performance is not especially efficient. There are a number of techniques for
managing non-integer representations including fixed-point techniques.
Physical types. The last scalar type – that of the physical type – is
provided to give context to real-world quantities. The only predefined physical
type is that of time; however, numerous libraries are available to define other
physical types such as distance, voltage, and current.
Users can also define custom physical types.
Similar to floating types, physical types are not synthesizable, but rather
intended for simulation. Using physical types, board-level and system-level
simulations are possible (including modeling of analog-to-digital converters,
transistors, and other devices).
The most commonly used physical type is that of time which is almost always
used in test benches to generate clocks and other stimuli.
Composite types define collections. These collections could be an ordered
grouping of the same type of scalar object – arrays, or of different types of
scalar objects and/or other composite types – records.
A commonly used array of std_logic is to define a byte: "array(7 downto 0) of
std_logic" Of course the std_logic_vector type is a shorthand for this and is
equivalent to "std_logic_vector(7 downto 0)".
- The key difference between the aggregate and the slice is that the slice can
refer to only an individual item or a contiguous region within an array.
Aggregates can specify non-contiguous regions in arrays as well as be applied
to records (next section; see Figure 3.10).
- A record is a collection of different types of scalar objects or other
composite types for that matter.8
- There are seven categories of predefined operators in VHDL. They are as
Binary logical operators (and, nand, or, nor, xor, xnor)
Relational operators (1⁄4, /1⁄4, <, <1⁄4, >, >1⁄4)
Shifting operators (sll, srl, sla, sra, rol, ror)
Addition operators (þ, À, &)
Unary sign operators (þ, À)
Multiplying operators (), /, mod, rem)
Other operators (not, abs, )))
- The "&" operator is a concatenation symbol that joins two
std_logic/std_logic_vectors into a single std_logic_vector. The "&" operator
can also be used to join strings and characters.
Line 6: rotRightResult <= slvValue(2 downto 0) & slvValue(7 downto 3);
- Line 6: rotation can also be thought of as breaking the vector into two
pieces and swapping. Since we are looking to do a right rotation of three, we
take the lowest 3 bits and swap them with the upper 8–3 bits.
- the IEEE offers a collection of math libraries which can be found in the
MATH_REAL package which provides a great deal of support for the real data type
including logs, square roots, exponentiation, etc. This library is NOT
synthesizable, but can be used in synthesizable code to produce constants.
- The final value of the catenation operation must match the size and type of
the variable or signal that is on the left-hand side of the assignment
- Unconditional assignment (<=) Concurrent assignment statements can be
unconditional (absolute), where the operation described on the right-hand side
of the assignment statement is assigned to the signal on the left-hand side
with no delay, always.
- Always use a "catch-all" condition to avoid accidental latch inference. Use
a final "else" when coding with the "when/else" construct. Use a final "when
others" when coding with the "with/select" construct.
- Important note: the "=>" [used in port maps] is
confusing as it seems to suggest a direction for connection. It does NOT! This
symbol indicates an association.
Output signals may be left disconnected (using the keyword "open" or tied to
a signal which is not driven otherwise a "multi-source" error will occur (a
When we instantiate a submodule, we are coding in the "structural" style,
that is, we are describing how various modules connect.
- Never drive a clock input with a signal that was generated by FPGA fabric
(LUTs or Flops)! Corollary: clocks may only be divided by special silicon
resources. Corollary: Use clock enables to have logic run at a lower rate.
- Simulate each module as you code from the bottom up so that you will have
confidence that your lower level modules are working properly before you tackle
the larger (i.e. more complex) higher-level modules.
- Best Design Practices calls for us to design from the "top-down" and code
from the "bottom-up"
Both post-Map and post-Place-and-Route simulations are generally not
performed for two reasons: first, they are very time-consuming for anything
larger than a small or medium design. Second, another tool – the Static Timing
Analyzer – is available. This tool analyzes all of the paths through the FPGA
against the timing constraints provided to the implementation tools and reports
unconstrained paths (paths that are not covered by a timing constraint) and
failing paths (paths that take too long to meet timing).
The UUT [unit under test] or DUT [device under test] represents the top level of the design
that you intend to synthesize.
- By making your process purely synchronous, you will only have one signal in
your process sensitivity list – the clock!
wait for <time>
wait until <event>
wait on <signal list>
- How Big Should a Module Be? A module should be only one ‘‘thought’’ in size.
Corollary – Any sufficiently large design should be decomposed into smaller
thoughts. Each thought should be a separate module, whether it can be
implemented as a component, process, or RTL statement. The lowest level of
thought should be the action. This implies that any action is the result of a
well thought out process.
- Variables are temporary storage mechanisms which reside within the process
- Use synchronous processes whenever possible. Many FPGA instructors and
designers have found that students and clients who view an FPGA as a "sea of
flip-flops interspersed with combinatorial logic" rather than "a ton of
combinatorial logic contaminated by registers" tend to build faster performing
designs and spend less time in debug.
- IEEE provides two functions in the STD_LOGIC_1164 package – rising_edge and falling_edge.
- This third example [of fully synchronous reset] is
preferred as it better aligns to best design practices and is more efficient to
implement in modern FPGA architecture.
If it makes sense to NOT have a reset for a process, then do so.
If a reset must be used, it should use positive logic ('1' = reset, '0' =
If a reset must be used, make it synchronous to the established clock.
- It is generally a bad practice to not include an ‘‘else’’ statement if all
possible cases are not covered. Within this final ‘‘else’’ clause should be an
assignment to target. Without this clause, if none of the if statements
evaluate to logic true, then a latch will be inferred. Due to the asynchronous
nature of a latch, they should be avoided as it can have a strong negative
effect on the performance.
- To achieve the fastest and most reliable performance, avoid using latches! It
is important to use the "when others" clause if all possibilities of the case
statement are not expressed – especially in an asynchronous process. Corollary
1: Use registers instead Corollary 2: completely cover all possible cases in
both "if/then/else" and "case" statements when using asynchronous
processes! Make every effort to avoid using asynchronous processes!
- "Mealy" type state machines tend to have fewer states than the "Moore" state
machines, but because of the use of combinatorial logic to form the outputs,
this type of state machine is more difficult to properly simulate (in that
there are more conditions that must be validated), and tend to have a lower
performance in FPGAs.
- The "Moore"-type state machine tends to have more states, with each state
producing one, and only one, set of outputs. This makes
the "Moore"-type state machine very easy to validate in simulation and performs
better in the register-rich FPGA environment.
- Whenever possible, write code so that it is
self-resetting so that no external reset is required. Corollary – apply
resets to modules whose outputs may produce undesirable effects downstream or
when self-resetting will prevent the process from reaching a usable state
before an upstream source begins providing data. Best Coding Practices: always
code for active high, synchronous resets as these best fit current FPGA
architectures (i.e. no additional resources are required and the highest
performance will be achieved). Corollary: some FPGA silicon resources require
active low resets. Code the active low reset as close to the silicon resource
instantiation/inference as possible.
- Generally, a state machine which must be coded for performance should NOT
include additional logic such as the adder, but rather enable a counter located
in a separate process.
- Let’s do a quick review of Generate and Generic statements:
1. Generics can be of any legal type or subtype.
2. Generics are used in the same way as constants are within a module.
3. Generic values can change from instantiation to instantiation, but are
constant within a module.
4. Top-level Generics can usually be set via synthesis tool options (GUI) or
command line arguments so that source code doesn't need to be modified.
5. Generate statements come in two flavors: conditional and looping.
6. Generate statements can wrap one or more concurrent statements – this
includes component instantiations, assignments, conditional assignments, and
7. Generic and Generate statements are often, but not always, used together to
make code more flexible for reuse.
Procedures are blocks of codes which take zero or more "arguments" and
perform a designated task. These tasks may change zero or more of the passed
"arguments" A procedure does not "return" a value as a function does, but
rather relies on modification of one or more of the passed arguments.
Functions are blocks of codes which take zero or more "arguments" and perform
a designated task. While these tasks may change zero or more of the "arguments"
they must return exactly one value of the designated type.
Whatever types, constants, or variables that are defined in this function or
procedure are only available within that function or procedure. Information can
only be made available from within the function or package by means of the
arguments or the return value in the case of functions.
There is a special category of functions and procedures known as "pure" and
"impure" Functions are "pure" by default, that is, if you neglect to specify
that the function or procedure is pure or not, VHDL assumes that the function
or procedure is pure. Pure, in this context, means that all the information
that flows into the function arrives via the arguments, and all the information
that flows out of the function are returned via the return statement and/or the
Conversely, an "impure" function or procedure either modifies a value not
listed in the parameter list, or uses a value not listed in the parameter list.
As a general rule, functions are used to modify a particular piece of data.
For example, computing the next value of a gray code sequence can easily be
computed with a function.
Procedures are used when several values must be changed or when no values are
returned. Examples of use might be a procedure for initializing a large set of
data, or writing specific messages to the simulation console.
- these Predefined Attributes can be applied to the commonly used
‘left – returns the leftmost value in the range
‘right – returns the rightmost value in the range
‘high – returns the largest value in the range (regardless of order)
‘low – returns the smallest value in the range (regardless of order)
‘range – returns the range of the array
‘reverse_range – the reversed range of an array
- Think of the package as containing the "shapes" of functions – much like a
header file may contain function prototype in C. It defines what the function
or procedure name is, what kind of parameters it takes, and in the case of a
function, what the function returns. The body contains a copy of what was
defined in the package except that it adds the functionality to the function or
- Naming convention is to end the name of each package with _pkg so that it is
readily recognized as a package. Although packages and their corresponding
package bodies may be in different files, most designers prefer to keep the package and its associated body in the
same file. Some designers choose to create a separate package that contains
all of the component definitions for a project, in this way if a component
changes, the change only needs to be made to the one package and wherever the
package is used. Packages are physically located in a separate set of
directories. Each directory contains the packages for a specific library.
It is important to note that not every synthesis tool will implement
multiplication and division for every possible argument. For example, some synthesis tools may only support multiplication by some
power-of-two as this is easily done by wire shifting and requires no logic.
Typical tricks for doing multiplication by a non-power-of-two constant would be
to break the single multiplication into a series of shift-and-adds. So,
multiplying by a constant "5" is the same as multiplying by 4 (left shift by
2), then adding itself.
the trivial case of dividing by a power-of-two can be
implemented simply by right-shifting a signal. More complex division,
such as fixed-point and floating-point math is available in the IEEE fractional
packages (IEEE 1076.3-2006). Not all aspects of these packages may be
efficiently implemented in synthesis.
- TEXTIO functions are not synthesizable and are used almost exclusively in
simulation to read data sets and to log output data.