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 olderstd_logic_arith,std_logic_unsignedandstd_logic_signedpackages 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_stdpackage for portable arithmetic. - Vendor synthesis guides, for example AMD Vivado UG901, for tool-specific coding rules.
- OSVVM and UVVM open-source verification libraries.