VHDL Coding Style Guidelines
(Adapted from Xilinx's
Coding Style Guidelines)
Table of Contents
Section 1 - Top-Down Design
Behavioural and Structural Code
"When creating synthesizable code (RTL), you should write two types of
code: behavioral RTL (leaf-level logic inference, sub-blocks) and
structural code (blocks) -- each exclusively in its own
architecture." - Xilinx
Essentially, this means that, rather than mixing behavioural and
structural VHDL in one descriptive file, you should separate your VHDL
code. Ideally, in creating a formal design that would be iterated
over and updated repeatedly, there should be no signal assignments.
In our environment, it is not a large problem to invert a signal or
merge two signals into a bus, but major behavioural code such as
processes and case statements should be contained within their own
behavioural descriptions and not in a structural file.
Always keep in mind that your VHDL code is not just a random
assortment of files, but rather a hierarchy of structural elements
specifically placed to create a well defined design.
Behavioural |
architecture
<arch-name>
of
<entity-name>
is
begin
output <= input_1
or
input_2;
end
<arch-name>;
|
Structural |
architecture
<arch-name>
of
<entity-name>
is
component
or_entity
port(
output: out std_logic;
input_1: in std_logic;
input_2: in std_logic
);
end component;
begin
or_1: or_entity
port map(
output => o,
input_1 => i_1,
input_2 => i_2
);
end <arch-name>;
|
Declarations, Instantiations, and Mappings
"It is important to use a consistent, universal style for such things
as entity declarations, component declarations, port mappings,
functions, and procedures." - Xilinx
- For signal assignments, port mappings and component
instantiations, the general rule is one line per signal. You
can exclude small components, but this rule will increase the
readability and understandability of your code.
- There are two ways to map signals to ports on your components.
The first is to list the signals in the order declared. The second is
to explicitly declare the mappings. It is preferential to always
explicitly declare port mappings . This makes it easier to
understand the mapping and easier to alter your code later.
Comments
"Liberal comments are mandatory to maintain reusable code. Although VHDL is sometimes considered to be self-documenting code, it requires liberal comments to clarify intent, as any VHDL user can verify." - Xilinx
When people describe VHDL as 'self documenting', generally they
are talking about understanding that a signal assignment has occurred,
not understanding the idea behind the assignment. As far as
understanding reasoning for signal assignments, VHDL alone cannot
possibly fully describe what is going on without comments. This is
why there are three general rules for commenting:
- Commenting entities can be an important tool for your
design. Knowing exactly what behaviours are (and aren't) taken care
of by an entity can help improve your design as well as its
readability. This should be done using a comment header.
Example: |
entity <entity-name> is
begin
port(
clk: in std_logic;
-- system clock
data: out std_logic_vector(7 downto 0);
-- data output sent to next
-- component
);
end <entity-name>
|
- Commenting processes and functions is an important
aspect of documenting your code, and should be done with a
header describing the responsibility of that block of
code
Example: |
--------------------------------------------
-- ProcessName:
-- A description of the process and
-- what it accomplishes goes here
--------------------------------------------
ProcessName: process ()
begin
. . .
|
- Commenting statements within processes and functions is
important so that the reader may understand not only that you have
made a signal assignment, but also your reasoning behind it with
respect to the entire code block. You need not comment every
statement, but those that are key to the behaviour of the component
should generally be commented
Example: |
data <= (others => '0');
-- reset the data to 0's
|
Indentation
"Proper indentation ensures readability and reuse. Therefore,
a consistent style is warranted. Many text editors are
VHDL-aware, automatically indenting for 'blocks' of code,
providing consistent indentation. Emacs and CodeWright are
two of the most common editors that have this
capability. Figure 13-7 shows an example of proper
indentation. Proper indentation greatly simplifies reading
the code. If it is easier to read, it is less likely that
there will be coding mistakes by the designer." - Xilinx
Indentation is an important aspect of VHDL. Knowing where
sections of code begin and end is very important in creating
readable updateable code. Both the Foundation and the Sonata
editors have the abilities to indent code, so this should not be
too much of a problem. As for when to indent, the simple rule
of thumb is that any section of code that is marked by an
end (and, optionally, a begin) keyword should have
its body indented. This includes component declarations,
process statements, if statements, case statements, entity
declarations and architectures.
Naming Conventions
"Naming conventions maintain a consistent style, which
facilitates design reuse. If all designers use the same
conventions, designer A can easily understand and use designer
B's VHDL code." - Xilinx
These are, once again, general rules, but they are designed to
make your code easier to read, understand, and debug. By
following these rules, it also makes it easier for others to
understand your code.
- Processes
- Processes should have descriptive
names to make clear their purposes and make it easier to identify
problematic lines in simulation
- Enitities, Architectures, Functions
- Names should describe, in some way, what the block of code
does
- Names should be lowercase and use underscores to separate
words
- Entities should have unique names. Architecture need not have
unique names as they are linked to entities, but should have a name
resembling their entity (eg. 'entity_arch' or
'entity_rtl')
- Signal Naming Conventions
- once again, use lowercase names with underscores
- use '<signal_name>_addr' for addresses and
'<signal_name>_clk' for clocks
- for active-low signals, using '<signal_name>_l' is
preferred
- do not use '_in' and '_out' suffixes for port signal
names. when these are connected to other signals with similar
suffixes, your code can become very confusing.
- you should use '_i' as a suffix when using a signal
that is an internal representation of a port. (eg. if
'output' is your port, 'output_i' is the
internal signal representing it)
Section 2 - Signals and Variables
Casting
"The most common problem [with signals] is that signals can be
various data types. The problem in VHDL is "casting
"
from one data type to another. Unfortunately, no single
function can automatically cast one signal type to another.
Therefore, the use of a standard set of casting functions is
important to maintain consistency between designers" - Xilinx
Because VHDL is a strongly typed language, casting from one type
to another is a requirement and can, at times, be difficult.
There are a number of packages available with functions used for
casting. In this lab, we have chosen to use the
ieee.numeric_std package. It comes with all the
functions that you will need and is a standardly available
package. The table below shows pertinent castings that you may
need for this lab:
From |
To |
Function |
std_logic_vector |
unsigned |
unsigned(std_logic_vector) |
unsigned |
integer |
to_integer(unsigned) |
integer |
unsigned |
to_unsigned(integer, no_of_bits) |
unsigned |
std_logic_vector |
std_logic_vector(unsigned) |
Rules for Signals
"The rules for signals are not complex" - Xilinx
There are very few rules for signals:
- Inverted Signals
- Whenever possible, use active high signals. This makes
your code easier to understand and leads to better consistency.
- Entity Ports
- Inputs can only be read, they cannot be assigned
- Outputs can only be assigned, they cannot be read
- Inout ports are useful when signals need to be read and assigned,
but are used very rarely (eg. data lines for RAM when reading and
writing must occur)
- Internal Signals
- A signal should only be assigned in one process
- For unclocked processes, never assign to a signal more
than once
- For clocked processes, never assign a signal
outside of the reset or clock edge statements
-
note: the above are guarded against by the Foundation
synthesis tool. If you have done any of the above, your design will
not implement.
- Process Sensitivity List
- For a combinatorial process, all signals that are read should be
included in the sensitivity list
- For a clocked process, you need only the clock and reset signals
in the sensitivity list
Rules for Variables
"Variables are commonly not understood and are therefore not
used. Variables are also commonly used and not understood.
Variables can be very powerful when used correctly. This
warrants an explanation of how to properly use variables" -
Xilinx
When combinatorial signals are to be used within a process, most
often a designer will use variables. Variables, however, are
treated quite differently than signals during synthesis and
implementation.
Variables are not synthesized necessarily as physical wires
(whereas signals are). As such, their assignments are not
purely combinatorial. Assignment to a variable depends on its
order in a sequence of statements. The following is two uses of
variables:
Correct Variable
Assignment |
Incorrect Variable
Assignment |
variable := a and b;
c <= variable or e;
d <= variable or f;
|
c <= variable or e;
d <= variable or f;
variable := a and b;
|
The assignment on the left writes to the variable before reading
from it. As a result, the variable simply becomes a wire
connecting the output of an
andgate to the inputs of two
or gates. The assignment on the right, however, reads
the variable before writing to it. This tells the synthesis
tool that the value being read is the previous value of the
variable. As a result, the value is stored sequentially and
this leads to a
'latch inferred' warning.
Section 3 - Synchronous Design
Clocking
"In a synchronous design, only one clock and one edge of the
clock should be used..." - Xilinx
An important technique in design is the synchronization of
designs with a clock. A regularly alternating signal, the clock
provides a time during which signals can be checked and memory
units (registers, flip-flops, etc.) can be updated. There are a
number of considerations that must be made when using the clock,
however.
- When clocking a design, one clock signal and one clock
edge should be used.
- Generating internal clocks or gating clock signals is
generally problematic because of glitching and clock skew
problems
- Any clocked process should include an asynchronous reset
signal to generate initial values for signals that are being
set synchronously.
Finite State Machines
"Coding for Finite State Machines (FSM) includes analyzing
several tradeoffs." - Xilinx
FSMs are extremely useful tools in digital design. They used in
many different applications so there are some standard rules
regarding their implementation:
- In general, three processes are recommended for a state
machine: next-state decoding, output decoding and registering
state bits and outputs.
- Always explicitly define behaviour for all states and
outputs. If not, the results of synthesis may produce
unexpected results.
- It is generally suggested that one-hot encoding be used
for state machines. For example, a 5 state machine should use
5 bits to encode the states. Each state should have one bit
high and the others low.
Section 4 - Logic Level Reduction
Conditional Statements
"If-then-else and case statements can cause unwanted effects
in a design. Specifically, nested if-the-else and case
statements may cause extra levels of logic inference. This
occurs because if-then-else statements generally infer
priority-encoded logic. However, one level of an if-then-else
will not necessarily create priority-encoded logic. For that
matter, synthesis tools generally handle if-then-else or case
statements very well and create parallel logic rather than
priority-encoded logic." - Xilinx
Conditions in if-then-else statements very often are synthesized
as combinatorial logic (and gates, or gates, etc.). As a
result, nested statements will create deeper combinatorial logic
causing complications in timing, gate delays and could create a
priority encoding situation that is undesired by the designer.
Therefore, there are a few simple guidelines to follow when
using conditional statements:
- Avoid, when possible, nesting conditional (if-then-else
or case) statements
- Try to create exclusive conditional cases, rather than
overlapping ones. This will limit any unintentional
priority-encodings
- Always make explicit all cases (with else or
when others conditions to eliminate unwanted logic
[Return to CMPUT 329 Lab Home Page]