VHDL 101 - Everything You Need to Know to Get Started
Author:
William Kafig
Pub Year:
2011
Source:
Read: 2017-12-08
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.
Thoughts:
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
well described.
Completing Eduvance the VHDL
tutorial I watched as I read FPGA 101 proved to be a nice supplement for
both books.
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.
Notes:
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
Preface
page ix:
-
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
say...
page 1:
-
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.
page 2:
- 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.
page 3:
- 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.
page 4:
-
[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,
are required.
-
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"
page 7:
'''
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
'''
page 12:
page 16:
page 20:
- 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
argument list.
page 21:
page 22:
- VHDL is a file-oriented language and, usually, there is just one entity and
one architecture3 described within a file.
page 23:
-
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.
page 24:
- One of many signal-naming conventions:
page 26:
- "Work" library that represents the location to which all compiled code is
located5 and the "STANDARD" library which contains basic data types.
page 27:
- 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.
page 28:
-
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.
page 29:
- 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.
page 30:
- 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
page 32:
| signal input_from_laser_gyro: std_logic_vector(15 downto 0) := (others=>’0’);
|
page 33:
-
- When naming a signal, use _n to show that a signal or variable is active
low.
-
- Make every effort to code resets within the FPGA as active high since this
generates the most efficient FPGA code.
page 34:
page 35:
-
although VHDL is a case insensitive language, when enumerated types are
enclosed by single quotes, the case does become
important!
-
As a designer, you will most likely use your own enumerated types for naming
various states within a state machine.
page 36:
-
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
page 37:
-
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.
page 38:
-
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)".
page 39:
page 41:
- 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).
page 42:
page 43:
- A record is a collection of different types of scalar objects or other
composite types for that matter.8
page 47:
- There are seven categories of predefined operators in VHDL. They are as
follows:
| 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, )))
|
page 48:
page 52:
page 53:
- 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.
page 54:
- 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.
page 55:
- 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
operator.
page 63:
- 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.
page 68:
- 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.
page 69:
page 70:
page 72:
- 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.
page 73:
-
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
short).
-
When we instantiate a submodule, we are coding in the "structural" style,
that is, we are describing how various modules connect.
page 75:
page 77:
- 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.
page 81:
page 84:
- 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"
page 85:
-
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.
page 88:
page 93:
- By making your process purely synchronous, you will only have one signal in
your process sensitivity list – the clock!
page 95:
| wait
wait for <time>
wait until <event>
wait on <signal list>
|
page 96:
page 97:
page 100:
- 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.
page 101:
- Variables are temporary storage mechanisms which reside within the process
page 110:
- 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.
page 111:
- IEEE provides two functions in the STD_LOGIC_1164 package – rising_edge and falling_edge.
page 112:
- 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.
page 113:
-
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' =
operational).
-
If a reset must be used, make it synchronous to the established clock.
page 115:
- 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.
page 117:
- 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!
page 121:
page 123:
- "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.
page 125:
- 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.
page 130:
- 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.
page 133:
page 137:
page 141:
page 146:
page 158:
- Let’s do a quick review of Generate and Generic statements:
1
2
3
4
5
6
7
8
9
10
11
12 | 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
even processes.
7. Generic and Generate statements are often, but not always, used together to
make code more flexible for reuse.
|
page 161:
page 165:
-
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.
page 166:
-
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
arguments.
-
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.
page 167:
page 168:
page 171:
-
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.
page 173:
page 177:
- these Predefined Attributes can be applied to the commonly used
std_logic_vector.
| ‘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
|
page 179:
page 181:
- 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
procedure.
page 182:
page 183:
page 184:
- 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.
page 188:
page 189:
-
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.
page 190:
- TEXTIO functions are not synthesizable and are used almost exclusively in
simulation to read data sets and to log output data.
page 195: