Circuit Design 02

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:

  1. Library and package declarations
  2. Entity declaration
  3. 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 and numeric_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.