Understanding VHDL Code Structure: A Comprehensive Guide #
VHDL (VHSIC Hardware Description Language) is one of the primary languages used for designing digital systems and programmable logic devices. Whether you’re new to VHDL or looking to refresh your knowledge, understanding its code structure is essential for creating efficient and maintainable designs. This article explores the fundamental components that make up VHDL code and provides practical examples to help you get started.
The Building Blocks of VHDL Code #
At its core, a basic VHDL code consists of three main sections:
- Library and package declarations
- Entity declaration
- Architecture body
These sections work together to define both the interface and functionality of your digital circuit. Let’s examine each component in detail.
Library and Package Declarations #
Libraries in VHDL are collections of commonly used code that can be shared across different designs. The standard VHDL libraries include:
Library std
- Contains the
standard
package with basic data types like BIT, INTEGER, BOOLEAN, and CHARACTER - Includes the
textio
package for handling text and files
Library ieee
- Contains the widely used
std_logic_1164
package that defines the 9-value STD_LOGIC data type - Includes packages like
numeric_std
andnumeric_bit
for arithmetic operations
To make these packages available in your design, you need to declare them at the beginning of your code:
LIBRARY ieee;
USE ieee.std_logic_1164.all;
It’s worth noting that both the std
library and the work
library (where your project files are stored) are visible by default, so you don’t need to explicitly declare them.
Entity Declaration #
The ENTITY section defines the interface of your circuit - essentially its inputs and outputs. The main part of an entity is the PORT declaration, which lists all the input and output ports of the circuit:
ENTITY entity_name IS
PORT (
port_name: port_mode signal_type;
...
);
END ENTITY entity_name;
Each port has three components:
- A name
- A mode (IN, OUT, INOUT, or BUFFER)
- A data type
The port modes determine the direction of data flow:
- IN: truly unidirectional input
- OUT: truly unidirectional output
- INOUT: bidirectional port
- BUFFER: output port that can also be read internally
For example, a simple NAND gate entity might look like:
ENTITY nand_gate IS
PORT (a, b: IN BIT;
x: OUT BIT);
END ENTITY;
Besides the PORT section, an entity can also contain a GENERIC section for declaring constants, a general declarative part, and a section with passive statements.
Architecture Body #
The ARCHITECTURE section contains the actual code that describes how your circuit should function. From this description, the hardware compiler infers the actual circuit:
ARCHITECTURE architecture_name OF entity_name IS
[architecture_declarative_part]
BEGIN
architecture_statements_part
END ARCHITECTURE architecture_name;
The architecture has two parts:
- A declarative part (optional) for internal signals, constants, etc.
- A statements part (required) containing the actual VHDL code
For our NAND gate example, a simple architecture would be:
ARCHITECTURE arch OF nand_gate IS
BEGIN
x <= a NAND b;
END ARCHITECTURE;
Working with Generic Parameters #
Generic parameters allow you to create more flexible and reusable designs. They’re essentially constants that can be easily modified for different applications:
GENERIC (
constant_name: constant_type := constant_value;
...
);
For example, you could parameterize a circuit to handle different data widths:
ENTITY my_entity IS
GENERIC (m: INTEGER := 8);
PORT (data: IN STD_LOGIC_VECTOR(m-1 DOWNTO 0);
...);
END my_entity;
When this component is instantiated in another design, the generic values can be overridden using a GENERIC MAP declaration.
Practical VHDL Examples #
Let’s look at some practical examples to see how these components work together:
Example 1: Compare-Add Circuit #
This circuit compares two 3-bit unsigned values and also adds them:
LIBRARY ieee;
USE ieee.std_logic_1164.all;
ENTITY comp_add IS
PORT (a, b: IN INTEGER RANGE 0 TO 7;
comp: OUT STD_LOGIC;
sum: OUT INTEGER RANGE 0 TO 15);
END ENTITY;
ARCHITECTURE circuit OF comp_add IS
BEGIN
comp <= '1' WHEN a>b ELSE '0';
sum <= a + b;
END ARCHITECTURE;
This simple design performs two operations concurrently: it compares the inputs (producing a single-bit output) and adds them (producing a 4-bit output to avoid overflow).
Example 2: D-type Flip-Flop #
A D-type flip-flop (DFF) is a fundamental storage element in digital circuits:
LIBRARY ieee;
USE ieee.std_logic_1164.all;
ENTITY flip_flop IS
PORT (d, clk, rst: IN STD_LOGIC;
q: OUT STD_LOGIC);
END ENTITY;
ARCHITECTURE flip_flop OF flip_flop IS
BEGIN
PROCESS (clk, rst)
BEGIN
IF (rst='1') THEN
q <= '0';
ELSIF (clk'EVENT AND clk='1') THEN
q <= d;
END IF;
END PROCESS;
END ARCHITECTURE;
This example introduces a PROCESS, which is needed to create sequential (clocked) circuits. The flip-flop copies the input to the output when the clock transitions from ‘0’ to ‘1’ and includes an asynchronous reset.
Example 3: Generic Address Decoder #
This example showcases the power of generic parameters with an N-bit address decoder:
ENTITY address_decoder IS
GENERIC (N: NATURAL := 3);
PORT (address: IN NATURAL RANGE 0 TO 2**N-1;
ena: BIT;
word_line: OUT BIT_VECTOR(2**N-1 DOWNTO 0));
END address_decoder;
ARCHITECTURE address_decoder OF address_decoder IS
BEGIN
gen: FOR i IN address'RANGE GENERATE
word_line(i) <= '1' WHEN ena='0' ELSE
'0' WHEN i=address ELSE
'1';
END GENERATE;
END address_decoder;
This design is completely generic - by simply changing the value of N, you can create address decoders of different sizes without modifying the code.
VHDL Coding Guidelines #
When writing VHDL code, especially for large projects or team collaborations, following consistent coding guidelines is important:
- Use meaningful signal names for better readability
- Specify each signal in the entity on a separate line
- Use GENERIC declarations for values that might change
- Prefer STD_LOGIC and STD_LOGIC_VECTOR for interface signals
- Use descending indexes for vectors (MSB on the left)
- Be consistent with capitalization - many developers use uppercase for reserved words
- Use separating lines or blank lines to organize your code
- Avoid using BUFFER mode when possible
- Before designing, always ask yourself whether the circuit is combinational or sequential
VHDL 2008 Enhancements #
VHDL 2008 introduced several improvements to the language, including:
- Expanded packages (standard, textio, std_logic_1164, and numeric_std)
- New packages (numeric_std_unsigned, numeric_bit_unsigned, env, fixed_pkg, and float_pkg)
- Enhanced declaration options in ENTITY and ARCHITECTURE
- Extended GENERIC declarations to include generic types and subprograms
- Support for package generics and uninstantiated packages
Conclusion #
Understanding VHDL code structure is the foundation for creating efficient digital designs. By mastering the three main components - library declarations, entity, and architecture - you’ll be well-equipped to implement a wide range of digital circuits. As you progress, make use of generic parameters for more flexible designs and follow consistent coding guidelines for maintainable code.
Remember that VHDL code is inherently concurrent, which makes it fundamentally different from sequential programming languages. For sequential behavior, such as in flip-flops, you’ll need to use constructs like PROCESS to force sequential execution.
With this knowledge and the provided examples, you’re ready to start creating your own VHDL designs with confidence.