VHDL · Chapter 1.7 · Foundation
Libraries and the use Clause
Every VHDL file opens with a few lines about libraries and use clauses, and most beginners copy them without knowing what they do. This lesson demystifies them. A library is a named shelf of already-compiled design units, the library keyword makes a shelf visible, and the use clause imports names from a package so you can write short names instead of a long qualified path. You will see where the standard-logic type actually comes from, why the numeric package is the right home for arithmetic, what the work library means for your own code, and why packages are how a design shares its types, constants, and functions across many files. By the end these opening lines will make sense instead of being magic you paste in.
Foundation13 min readVHDLLibrariesuse clauseieeenumeric_stdPackages
1. Intuition — shelves of compiled parts
When a tool analyses your VHDL, it does not leave the results in your .vhd file — it
files each compiled design unit onto a named shelf called a library. A library is
simply a collection of already-compiled units (entities, architectures, packages) that
other code can refer to by name.
Two shelves are always in the room. work is your shelf — everything you compile
lands there by default. ieee is the standard shelf the tool vendor ships,
holding the packages that define std_logic, vector arithmetic, and more. A use
clause is how you say "reach onto that shelf and bring these names into scope so I do
not have to spell out the full path every time."
2. The two lines you write in almost every file
library ieee; -- make the ieee shelf visible
use ieee.std_logic_1164.all; -- bring in std_logic, std_logic_vector, ...
use ieee.numeric_std.all; -- bring in unsigned, signed, + - arithmeticlibrary ieee;declares that you intend to use theieeelibrary. (You never writelibrary work;orlibrary std;— they are always implicitly visible.)use ieee.std_logic_1164.all;imports every name (.all) from thestd_logic_1164package: thestd_logictype,std_logic_vector,rising_edge, and the logic operators on them. Without it, the typestd_logicis simply unknown.use ieee.numeric_std.all;imports theunsignedandsignedtypes and their arithmetic. This is the standard, portable way to do math on vectors.
You could instead write the fully qualified name every time —
ieee.std_logic_1164.std_logic — but nobody does; the use clause exists precisely so
you can write std_logic.
3. Reaching your own shelf — work.<pkg>.all
Anything you compile is on work. When you collect shared definitions into a package
and want them in another file, you import them the same way — just from work:
library ieee;
use ieee.std_logic_1164.all;
package my_types_pkg is
constant BUS_W : integer := 16; -- shared constant
subtype word is std_logic_vector(BUS_W-1 downto 0); -- shared type
end package my_types_pkg;library ieee;
use ieee.std_logic_1164.all;
use work.my_types_pkg.all; -- bring BUS_W and `word` into scope
entity datapath is
port (
a : in word; -- `word` came from your package
y : out word
);
end entity datapath;Because the package is on work, you reach it with work.my_types_pkg.all. The
ieee packages live on the ieee shelf; yours live on work. Same mechanism, two
shelves.
4. Visibility, not timing — the right artifact
A use clause changes what names are visible while compiling, so the honest way to
picture it is a visibility diagram, not a waveform. Each source file declares which
shelves it sees and which packages it imports; the compiler then resolves every type
and function name against those imports:
There is deliberately no waveform here: libraries and use clauses resolve names
before simulation exists. A waveform would teach nothing about visibility.
5. Why packages matter
Packages are why libraries are useful. A package is a shared header that holds types, constants, and functions so a whole project agrees on them in one place:
- Types — a
wordsubtype or a protocol record, defined once and used by every block, so widths never drift. - Constants —
BUS_W, address maps, opcode encodings: change the value in the package, every file follows. - Functions — helpers like a parity reduction, written once and called everywhere.
std_logic_1164 and numeric_std are just very well-known packages on the ieee
shelf — the same idea you use for your own shared definitions on work. (Authoring
packages in depth — the package body, compile order, visibility rules — is its own
later lesson; here you only need to know what a use clause is reaching for.)
6. Common mistakes & what to watch for
- Forgetting
use ieee.std_logic_1164.all;. Thenstd_logicis undefined and the file will not compile — the classic first error. - Writing
library std_logic_1164;.std_logic_1164is a package, not a library. The library isieee; the package is imported withuse. - Mixing
numeric_stdwith the legacy*_arithpackages. Picknumeric_stdand stay consistent, or you invite ambiguous-overload and portability problems. - Expecting
useto compile the package for you.useonly makes names visible; the package must already be analysed into its library first (the subject of the next lesson).
7. Summary & next step
A library is a named shelf of compiled units: work is yours, ieee is the
standard one. library makes a shelf visible and use imports a package's
names into scope — pure compile-time resolution, which is why a visibility diagram, not
a waveform, is the right picture. In nearly every file you import std_logic_1164 and
numeric_std; your own shared types and constants travel the same way via
work.<pkg>.all.
That phrase "the package must already be analysed first" is the hinge into the next lesson: compilation, elaboration, and the build flow — the order in which units are analysed into libraries, how an entity binds to its architecture, and why getting the order wrong produces "cannot find unit" errors.