Skip to content

VHDL · Chapter 1.9 · Foundation

Compilation, Elaboration, and the Build Flow

VHDL does not go straight from text to hardware. It passes through three distinct stages, analysis or compile, then elaboration, and then simulation or synthesis, and most confusing VHDL errors come from not knowing which stage you are in. This lesson builds the mental model that separates serious HDL engineers from beginners: what each stage actually does, why compile order is mandatory in VHDL, how an entity is bound to an architecture during elaboration, and how to read cannot-find-unit and unresolved-binding errors as precise signals about which stage failed and why. Once you can name the stage, the tool messages stop being mysterious and start pointing straight at the fix.

Foundation16 min readVHDLCompilationElaborationBuild FlowCompile OrderDebugging

1. Intuition — three stages, not one

Coming from software, you expect "compile, then run." VHDL has a stage in the middle, and skipping over it is why the tools seem to throw errors at surprising times. The journey from source to a running model is:

  1. Analysis (compile) — each design unit is checked on its own and filed into a library. Think "proofread each part and put it on the shelf."
  2. Elaboration — the tool picks a top unit and builds the design: it binds every entity to an architecture, expands the hierarchy, resolves generics, and creates the signals. Think "assemble the parts into one machine."
  3. Simulation / synthesis — only now does the elaborated machine run (simulator) or get mapped to gates (synthesiser).

2. The build flow — and what each stage catches

The stages run in order, and each one catches a different class of error. Knowing which stage rejected your code tells you where to look:

Analysis then elaboration then simulation or synthesis, with the errors each stage catchesAnalysis (compile)units → librarysyntax · type ·undeclared nameper-unit errorsElaborationassemble the designcannot find unit ·unbound · genericwiring errorsSimulate / Synthesizerun or map to gatesassertion · timing ·runtimebehaviour errors12
VHDL reaches hardware in three ordered stages. ANALYSIS compiles each design unit independently and stores it in a library — it catches syntax errors, type mismatches, and references to names that are not visible. ELABORATION assembles a chosen top unit into a full design — it binds entities to architectures, resolves generics, and is where 'cannot find unit' and 'unresolved binding' surface. SIMULATION/SYNTHESIS runs or maps the assembled design. An error's stage tells you its cause.

There is no waveform on this page on purpose: compilation and elaboration happen before any signal has a value. The build flow itself is the artifact that teaches the concept.

3. Analysis files units into a library

Analysis takes one design unit at a time, checks it, and stores the compiled result in a library (work by default). It verifies syntax, type correctness, and that every name it references is already visible — which means every unit it depends on must already be analysed. That requirement is the source of VHDL's compile-order rules:

  • a package body needs its package analysed first,
  • an architecture needs its entity analysed first,
  • anything that uses a package needs that package analysed first,
  • a design that instantiates another entity needs that entity analysed first.

VHDL does not hunt around your files to satisfy these for you — you (or your script / Makefile) must present units in dependency order.

4. A small project, in dependency order

Consider a counter that uses a shared constants package, wrapped by a top, exercised by a testbench. The arrows below are "depends on / must be compiled after":

Dependency order: package, then entity, architecture, top, testbench1 · const_pkgno deps2 · counter (entity)uses const_pkg3 · counter (arch)implements entity4 · topinstantiates counter5 · testbenchdrives top12
A dependency graph fixes the compile order. The constants package has no dependencies, so it is analysed first. The counter entity uses the package; its architecture implements the entity; the top instantiates the counter; the testbench drives the top. Analysis must proceed left to right — every unit is compiled only after the units it references are already in the library.

A compile script simply lists them in that order:

compile order (e.g. with a generic analyzer)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
analyze const_pkg.vhd      # 1 — package first (nothing depends earlier)
analyze counter.vhd        # 2,3 — entity then its architecture
analyze top.vhd            # 4 — instantiates counter (needs it analysed)
analyze tb.vhd             # 5 — drives top
elaborate tb               # build the design rooted at the testbench
simulate                   # run it
const_pkg.vhd — analysed first, depends on nothing of ours
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
 
package const_pkg is
  constant WIDTH : integer := 4;
end package const_pkg;
counter.vhd — entity then architecture; both need const_pkg
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.const_pkg.all;                 -- needs const_pkg already analysed
 
entity counter is
  port (
    clk : in  std_logic;
    q   : out std_logic_vector(WIDTH-1 downto 0)
  );
end entity counter;
 
architecture rtl of counter is          -- needs the entity above
  signal q_i : unsigned(WIDTH-1 downto 0) := (others => '0');
begin
  process (clk)
  begin
    if rising_edge(clk) then
      q_i <= q_i + 1;
    end if;
  end process;
  q <= std_logic_vector(q_i);
end architecture rtl;

5. Elaboration binds and assembles

Once the units are on the shelf, elaboration takes a top unit and builds the actual design tree. The key act is binding: for every entity in the hierarchy, the tool selects an architecture to use (by default the most recently analysed one, unless a configuration says otherwise). It also expands each instance, fixes every generic to a concrete value, and creates the signal objects the simulator will drive.

This is why an entity can compile perfectly yet fail at elaboration: analysis only checks the entity exists and is well-formed; binding it to an architecture happens later. If no architecture for that entity is in the library, analysis was happy but elaboration cannot assemble the design — and you get an unbound / unresolved binding error.

6. Compile-time vs elaboration-time errors

You see…StageUsual cause
"identifier std_logic not declared"analysismissing use ieee.std_logic_1164.all;
"unit const_pkg not found" / "cannot find"analysispackage not analysed yet, or wrong library / order
type / width mismatch in an assignmentanalysisthe expression and target types disagree
"cannot find entity/architecture for counter"elaborationentity analysed but its architecture was not
"instance u_counter is unbound"elaborationno matching component/entity binding
generic / port-map association errorelaborationhierarchy wiring or generic value is inconsistent
assertion fired / wrong waveformsimulationthe design is wrong, not the build

The pattern: "not declared / not found / type" → analysis. "cannot find unit / unbound / generic / port map" → elaboration. "it ran but misbehaved" → simulation.

7. What goes wrong when compile order is wrong

The same correct source files fail loudly if presented in the wrong order — the code is fine, the order is not:

  • Architecture before entity. analyze counter_arch before the entity → "entity counter not found." The architecture cannot be checked without its entity on the shelf.
  • Using a package before analysing it. analyze counter.vhd before const_pkg.vhd → "const_pkg not found" at the use line. Fix the order, not the code.
  • Missing package body. A package that declares a function but whose body was never analysed: the declarations compile, but elaboration fails to resolve the function — a stage-2 failure for a stage-1-looking file.
  • Stale library. You edited const_pkg.vhd but only re-analysed counter.vhd; the library still holds the old package. Symptoms look impossible until you re-analyse the dependency.

The debugging habit that follows: when you read "cannot find unit X", do not stare at the line — ask "was X analysed into this library, before this unit, and is it still current?" When you read "unbound" or "no architecture", ask "did the architecture (or component binding) actually get compiled?" The error names its stage; the stage names the fix.

8. Common mistakes & what to watch for

  • Assuming the tool finds dependencies for you. It does not — order is your job (or your Makefile's).
  • Reading every error as a syntax error. An elaboration error means the units are fine but the design will not assemble — a different fix entirely.
  • Forgetting to re-analyse a changed dependency. Edit a package, re-analyse it and everything downstream, or the library stays stale.
  • Ignoring which library a unit landed in. work is default, but multi-library projects can analyse a unit into the wrong shelf and then "cannot find" it.

9. Summary & next step

VHDL reaches hardware in three ordered stages: analysis compiles each unit and shelves it in a library, elaboration binds entities to architectures and assembles a chosen top into a real design, and simulation/synthesis runs or maps it. Compile order is mandatory because analysis only succeeds when every referenced unit is already on the shelf — package before users, entity before architecture, dependency before dependent. Read an error by its stage and the cause is usually obvious: "not found / type" is analysis, "unbound / cannot bind" is elaboration, "misbehaved" is simulation.

With the build model in place, the foundations turn to the two faces of that final stage — simulation versus synthesis — and how the same source is read differently by each, which is where the rest of the track's RTL discipline begins.