HDLbits

Reference · Language overviews

HDL Language Overviews

Explore each language from its core building blocks to practical design patterns. These working-level references connect syntax and types to simulation, verification and synthesis.

VHDL · Language reference

VHDL Language Overview

14 chapters

Chapter index (14)

A working-level guide to the language: structure, types, operators, statements, hierarchy, patterns, testbenches and pitfalls.

1. Introduction

Item Information
Full name VHSIC Hardware Description Language (VHSIC = Very High Speed Integrated Circuit)
Standard IEEE 1076; editions 1987, 1993, 2002, 2008, 2019
Typing Strong and static; conversions must be explicit
Case sensitivity Not case-sensitive (Clk and clk are the same name)
Execution model Concurrent and event-driven; statements inside a process run in order
Design units entity, architecture, package, package body, configuration
Best known for Rigorous, self-documenting RTL in defense, aerospace, rail and medical work

VHDL is a hardware description language used to model, simulate and synthesize digital circuits. A VHDL description can be simulated to check behavior and, when written in the synthesizable subset, turned by a tool into gates for an FPGA or ASIC. This guide covers the whole language at a working level: structure, types, operators, statements, hierarchy, common design patterns, testbenches and pitfalls.

Typical design flow

  • Write entities and architectures, plus a testbench for each block.
  • Simulate to check behavior against expectations.
  • Synthesize the synthesizable subset into a netlist of gates and registers.
  • Implement (place and route) for the target FPGA or ASIC, then check timing.

Two ideas underpin everything else

Hardware is parallel: separate statements and processes describe circuitry that is always active. A signal is not a variable: it is a wire or register whose new value takes effect only after the current simulation step.

2. Lexical elements

Element Rule or example
Comments -- text to end of line; /* ... */ block comments from VHDL-2008
Identifiers Start with a letter; letters, digits and single underscores; no trailing or consecutive underscores
Time literal 10 ns, 2.5 us (value, space, unit)

3. Program structure

Design units and libraries

Unit Purpose
entity Declares the interface: ports and generics
architecture Describes the contents; an entity may have several
package / package body Shared types, constants, functions and components
configuration Selects which architecture or component binding to use
library / use Makes libraries and packages visible

Entity and architecture skeleton

library ieee;                                 -- make the IEEE library visible
use ieee.std_logic_1164.all;                  -- std_logic and std_logic_vector
use ieee.numeric_std.all;                     -- unsigned, signed and arithmetic

entity name is
    generic ( WIDTH : positive := 8 );        -- optional compile-time parameters
    port (
        clk  : in  std_logic;
        din  : in  std_logic_vector(WIDTH-1 downto 0);
        dout : out std_logic_vector(WIDTH-1 downto 0)
    );
end entity name;

architecture rtl of name is
    -- declarations: signals, constants, types, components, subprograms
begin
    -- concurrent statements: assignments, processes, instances, generates
end architecture rtl;

Port modes

Mode Meaning
in Read-only inside the entity
out Driven by the entity; readable inside from VHDL-2008
inout Bidirectional, used for tri-state buses
buffer Output that can also be read inside (older style)

4. Data types

Type Source Notes
bit, bit_vector standard Two values, ‘0’ and ‘1’
boolean standard false or true; result of comparisons
integer, natural, positive standard integer is a predefined signed type with an implementation-dependent range; natural and positive are subtypes (0..INTEGER’HIGH and 1..INTEGER’HIGH, respectively)
real standard Floating point; mostly for testbenches
character, string standard Text; used in reports
time standard Integer with a unit (fs to hr)
std_logic, std_logic_vector ieee.std_logic_1164 Nine values: U X 0 1 Z W L H -
unsigned, signed ieee.numeric_std Vectors that support arithmetic
enumeration user Named values, typically FSM states
array, record user Collections of same or different types

User-defined types

type    state_t  is (IDLE, RUN, DONE);                          -- enumeration
subtype nibble_t is std_logic_vector(3 downto 0);               -- constrained subtype
type    mem_t    is array (0 to 255) of std_logic_vector(7 downto 0);   -- array

type cmd_t is record                                            -- record
    addr : std_logic_vector(7 downto 0);
    data : std_logic_vector(7 downto 0);
end record;

signal   state : state_t := IDLE;
constant DEPTH : integer := 16;

Attributes

Attributes query information about a type or object: x'length, x'left, x'right, x'high, x'low, x'range, and for signals s'event. The function rising_edge(clk) is the standard way to detect a clock edge.

Type conversion

-- Closely related types: cast
slv_out <= std_logic_vector(uns_value);
uns_val <= unsigned(slv_in);

-- Integer conversions (numeric_std)
int_val <= to_integer(uns_value);
uns_val <= to_unsigned(42, 8);        -- value, width
uns_val <= resize(uns_val, 16);       -- change width

Use numeric_std

Use ieee.numeric_std. The older std_logic_arith, std_logic_unsigned and std_logic_signed packages are non-standard and should be avoided.

5. Operators

Listed from highest precedence to lowest.

Class Operators
Miscellaneous (highest) ** abs not
Multiplying * / mod rem
Sign (unary) + -
Adding + - & (concatenation)
Shift sll srl sla sra rol ror
Relational = /= < <= > >=
Logical (lowest) and or nand nor xor xnor

The logical operators share one precedence level, so mixed expressions need parentheses: write (a and b) or c. Assignment uses <= for signals and := for variables, constants and initial values.

6. Concurrent statements

Statements in an architecture body are concurrent: their order does not matter, and each describes hardware that is always active.

y <= a and b;                              -- simple assignment

y <= i0 when sel = "00" else               -- conditional assignment
     i1 when sel = "01" else
     i2 when sel = "10" else
     i3;

with sel select                            -- selected assignment
    y <= i0 when "00",
         i1 when "01",
         i2 when "10",
         i3 when others;

Other concurrent constructs are process (a block of sequential code), component or entity instantiation, generate (replication and conditional hardware) and concurrent assert.

7. Processes and sequential statements

A process runs whenever a signal in its sensitivity list changes. Inside it, statements run in order. Different processes run concurrently.

Process templates

-- Combinational process: VHDL-2008 process(all) avoids missing signals
comb_proc : process (all)
begin
    y <= '0';                          -- default value prevents a latch
    if en = '1' then y <= a; end if;
end process comb_proc;

-- Clocked process with synchronous reset
seq_proc : process (clk)
begin
    if rising_edge(clk) then
        if rst = '1' then q <= (others => '0');
        else              q <= d;
        end if;
    end if;
end process seq_proc;

-- Clocked process with asynchronous reset
async_proc : process (clk, rst_n)
begin
    if rst_n = '0' then
        q <= (others => '0');
    elsif rising_edge(clk) then
        q <= d;
    end if;
end process async_proc;

Control statements

if a = '1' then ... elsif b = '1' then ... else ... end if;

case opcode is
    when "00"   => result <= a + b;
    when "01"   => result <= a - b;
    when others => result <= (others => '0');
end case;

for i in 0 to 7 loop
    parity := parity xor data(i);
end loop;
-- also: while <cond> loop ... end loop;   exit;   next;   null;

Signal versus variable

Signal Variable
Assignment <= :=
Takes effect After the process suspends (next delta cycle) Immediately
Scope Architecture, visible to all processes Local to one process or subprogram
Models Wires and registers Temporary values inside sequential code

Wait and timing (simulation only)

wait for 10 ns;                    -- pause for a time
wait until rising_edge(clk);       -- pause for a condition
wait;                              -- suspend forever
y <= a after 2 ns;                 -- delayed assignment (ignored by synthesis)

8. Subprograms and packages

A function returns a value and is pure by default, so it cannot have externally visible side effects; an impure function may access external state and have side effects. A procedure can have in, out and inout parameters. Packages group reusable declarations.

package util_pkg is
    constant BUS_WIDTH : positive := 16;
    function is_even (n : natural) return boolean;
end package util_pkg;

package body util_pkg is
    function is_even (n : natural) return boolean is
    begin
        return (n mod 2) = 0;
    end function is_even;
end package body util_pkg;

-- usage:  library work;  use work.util_pkg.all;

9. Hierarchy and parameterization

-- Direct entity instantiation (no component declaration needed)
u_cnt : entity work.up_counter
    generic map (WIDTH => 4)
    port map (clk => clk, rst => rst, en => en, q => count);

-- Replication with for-generate
gen_bits : for i in 0 to 7 generate
    u_reg : entity work.dff port map (clk => clk, d => d(i), q => q(i));
end generate gen_bits;

-- Conditional hardware with if-generate
gen_opt : if USE_REG generate
    q <= reg_value;
end generate gen_opt;

Older code declares a component in the architecture and instantiates it with port map. Both styles are valid. Use named association (port => signal) rather than positional association to avoid wiring errors.

10. Common design patterns

Counter with enable and synchronous reset

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity up_counter is
    generic (WIDTH : positive := 8);
    port (
        clk, rst, en : in  std_logic;
        q            : out std_logic_vector(WIDTH-1 downto 0)
    );
end entity up_counter;

architecture rtl of up_counter is
    signal value : unsigned(WIDTH-1 downto 0) := (others => '0');
begin
    process (clk)
    begin
        if rising_edge(clk) then
            if rst = '1' then
                value <= (others => '0');
            elsif en = '1' then
                value <= value + 1;
            end if;
        end if;
    end process;

    q <= std_logic_vector(value);
end architecture rtl;

Finite state machine (two-process style)

type   state_t is (IDLE, RUN, DONE);
signal state, next_state : state_t := IDLE;
...
state_reg : process (clk)                    -- state register
begin
    if rising_edge(clk) then
        if rst = '1' then state <= IDLE;
        else              state <= next_state;
        end if;
    end if;
end process;

next_logic : process (all)                   -- next-state logic
begin
    next_state <= state;                     -- default: stay
    case state is
        when IDLE => if start  = '1' then next_state <= RUN;  end if;
        when RUN  => if finish = '1' then next_state <= DONE; end if;
        when DONE => next_state <= IDLE;
    end case;
end process;

busy <= '1' when state = RUN else '0';       -- output decode

Memory (inferred RAM)

type   ram_t is array (0 to 255) of std_logic_vector(7 downto 0);
signal ram : ram_t;
...
process (clk)
begin
    if rising_edge(clk) then
        if we = '1' then
            ram(to_integer(unsigned(addr))) <= din;
        end if;
        dout <= ram(to_integer(unsigned(addr)));    -- registered read
    end if;
end process;

11. Testbenches

A testbench is an entity with no ports. It instantiates the design under test, generates a clock, applies stimulus and checks results.

Minimal testbench

library ieee;
use ieee.std_logic_1164.all;

entity up_counter_tb is
end entity up_counter_tb;

architecture sim of up_counter_tb is
    signal clk : std_logic := '0';
    signal rst : std_logic := '1';
    signal en  : std_logic := '0';
    signal q   : std_logic_vector(3 downto 0);
begin
    dut : entity work.up_counter
        generic map (WIDTH => 4)
        port map (clk => clk, rst => rst, en => en, q => q);

    clk <= not clk after 5 ns;                      -- 100 MHz clock

    stim : process
    begin
        wait for 20 ns;
        rst <= '0';
        en  <= '1';
        wait for 100 ns;
        assert q /= "0000"
            report "counter did not count" severity error;
        report "test finished";
        std.env.finish;                             -- VHDL-2008
    end process stim;
end architecture sim;

assert ... report ... severity (note, warning, error, failure) makes a testbench self-checking. Open-source verification libraries such as OSVVM and UVVM add randomization, scoreboards and coverage.

12. Synthesis guidance and pitfalls

Topic Guidance
Latches Assign every output on every path of a combinational process; use default assignments
Sensitivity lists Use process(all) (VHDL-2008) for combinational logic; only the clock (and async reset) for clocked logic
Reset style Choose one style and use it consistently; synchronous reset is common on FPGAs
Arithmetic Use numeric_std for portable arithmetic. Avoid non-standard/vendor packages such as std_logic_arith, std_logic_unsigned and std_logic_signed in portable code
Multiple drivers std_logic is a resolved type, so multiple drivers are legal in simulation; conflicting driver values can resolve to X. Whether a multi-driver structure is synthesizable depends on the intended hardware and tool/target technology
Not synthesizable wait for, after, file I/O, most real math, assert (ignored)
Initial values Honored by most FPGA tools, ignored by most ASIC flows; do not depend on them for ASIC
Clock domains Signals crossing clocks need synchronizers or handshakes
Naming Use meaningful names; avoid temp; keep one consistent naming convention

13. Quick reference

Task VHDL
Rising clock edge if rising_edge(clk) then
All zeros vector (others => '0')
Concatenate a & b
Slice v(7 downto 4)
Vector to integer to_integer(unsigned(v))
Integer to vector std_logic_vector(to_unsigned(n, 8))
Shift left by 1 shift_left(u, 1) (numeric_std) or u sll 1
Compare to zero v = (v'range => '0') or unsigned(v) = 0
Report a message report "text" severity note;
Instantiate u1 : entity work.name port map (...);

14. Standards and further reading

  • IEEE Std 1076 (VHDL Language Reference Manual); current edition IEEE 1076-2019.
  • IEEE Std 1076-2019 defines the current VHDL language standard. IEEE 1076.3-1997 is superseded; use the standardized numeric_std package for portable arithmetic.
  • Vendor synthesis guides, for example AMD Vivado UG901, for tool-specific coding rules.
  • OSVVM and UVVM open-source verification libraries.