VHDL Packages, numeric_std, and Project Structure
Small VHDL examples can live in one file. Real FPGA projects quickly need shared constants, reusable types, clear folder boundaries, and a build order that does not depend on memory. Packages and numeric_std are the foundation of that discipline.
Learning Objectives
By the end of this lesson, you should be able to:
- explain why
numeric_stdis preferred for VHDL arithmetic; - place constants, subtypes, records, and enumerations in a package;
- compile packages before files that depend on them;
- separate RTL, testbench, constraints, scripts, and generated artifacts;
- avoid package patterns that make reuse and review harder.
Why numeric_std Matters
std_logic_vector is a bundle of bits. It does not say whether the bits represent an unsigned number, a signed number, an address, a status field, or unrelated control flags. numeric_std makes that intent explicit:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
signal addr : unsigned(7 downto 0);
signal temp : signed(11 downto 0);
addr <= addr + 1;
temp <= temp - 16;
Use unsigned for natural binary values such as counters, lengths, and addresses. Use signed for two's-complement values. Convert to or from std_logic_vector at module boundaries when a port is intentionally bit-oriented.
Avoid Nonstandard Arithmetic Packages
Older code sometimes uses:
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
These packages are not the portable standard style. They can hide type mistakes and produce different behavior across tools. For beginner projects, a strict rule works well: arithmetic signals are unsigned or signed, and arithmetic comes from ieee.numeric_std.
A Simple Package
Packages are useful for names that multiple files must share. A UART project, for example, can keep its common widths and state type in one place:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package uart_pkg is
constant CLK_HZ : natural := 12_000_000;
constant BAUD_RATE : natural := 115_200;
constant DATA_BITS : positive := 8;
subtype baud_count_t is unsigned(15 downto 0);
subtype data_byte_t is std_logic_vector(DATA_BITS - 1 downto 0);
type uart_tx_state_t is (
IDLE,
START_BIT,
DATA_BITS_STATE,
STOP_BIT
);
end package uart_pkg;
Use the package in RTL like this:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.uart_pkg.all;
The work library is the default library where analyzed design units are stored by many VHDL tools, including GHDL.
What Belongs In A Package
Good package contents are stable and shared:
| Good candidates | Poor candidates |
|---|---|
| common constants | board pin names for one top module |
| reusable subtypes | temporary debug signals |
| record types | generated build output |
| enumerated states used by several files | one-off local state names |
| function declarations | vendor-specific constraints |
Keep board-specific constraints in constraint files, not in generic RTL packages. Keep package scope narrow enough that a reviewer can understand why each name is shared.
Compile Order
VHDL is order-sensitive. A file that uses a package must be analyzed after that package:
ghdl -a rtl/uart_pkg.vhd
ghdl -a rtl/baud_tick.vhd
ghdl -a rtl/uart_tx.vhd
ghdl -a tb/uart_tx_tb.vhd
ghdl -e uart_tx_tb
ghdl -r uart_tx_tb --vcd=uart_tx.vcd
If uart_tx.vhd uses work.uart_pkg.all, analyzing uart_tx.vhd first should fail. That failure is helpful because it exposes a real dependency.
Suggested Project Structure
fpga-uart/
rtl/
uart_pkg.vhd
baud_tick.vhd
uart_tx.vhd
uart_rx.vhd
top.vhd
tb/
uart_tx_tb.vhd
uart_rx_tb.vhd
constraints/
board.pcf
sim/
run_ghdl.sh
waves.gtkw
build/
generated files
rtl/ should be the synthesizable design. tb/ should be excluded from synthesis. constraints/ maps the design to a board. build/ should be disposable and usually ignored by version control.
Project Flow
This order keeps shared definitions visible before dependent code and keeps simulation-only files out of synthesis.
Worked Example: Width From A Package
Package:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package counter_pkg is
constant COUNT_WIDTH : positive := 8;
subtype count_t is unsigned(COUNT_WIDTH - 1 downto 0);
end package counter_pkg;
Counter:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.counter_pkg.all;
entity counter is
port (
clk : in std_logic;
rst : in std_logic;
en : in std_logic;
count : out std_logic_vector(COUNT_WIDTH - 1 downto 0)
);
end entity counter;
architecture rtl of counter is
signal count_r : count_t := (others => '0');
begin
process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
count_r <= (others => '0');
elsif en = '1' then
count_r <= count_r + 1;
end if;
end if;
end process;
count <= std_logic_vector(count_r);
end architecture rtl;
The width appears in one package constant, and the counter uses the package type consistently.
Practical Checks
Before committing a VHDL project, verify:
- every arithmetic file imports
ieee.numeric_std.all; - arithmetic signals are
unsignedorsigned, not plain vectors; - packages are compiled before dependent design units;
- testbench files are not included in synthesis file lists;
- generated files are outside source folders or ignored;
- package names are specific, such as
uart_pkg, not vague names likecommon; - changing a package constant triggers simulation and synthesis reruns.
Common Mistakes
- Putting unrelated constants into one giant package.
- Using
std_logic_vectorfor arithmetic and relying on nonstandard packages. - Hiding board assumptions inside reusable modules.
- Forgetting that VHDL compile order matters.
- Letting simulator-generated files pollute
rtl/. - Reusing a package name that conflicts with another library.
Summary
Good VHDL structure is not cosmetic. numeric_std makes arithmetic intent explicit, packages make shared definitions reviewable, and a repeatable compile order prevents hidden dependencies. Keep RTL, testbenches, constraints, and generated files separated so the project can grow without becoming fragile.
Next: Simulation and Testbenches.
Further Reading
- GHDL quick start
- GHDL invoking commands
- IEEE numeric_std package overview
- VHDLwhiz: packages in VHDL