VHDL · Chapter 4.6 · Concurrent Statements
Component Instantiation and Structure
Up to now an architecture has computed values through gates and assignments. It can also contain sub-blocks wired together, exactly like chips placed and connected on a board. A component instantiation is the concurrent statement that drops a copy of another design, an entity, into your architecture and connects it. Each instance is an independent piece of hardware running in parallel with everything else, and signals are the wires between instances. This lesson covers the two instantiation styles, the modern direct entity instantiation and the older component-declaration form, and how they assemble a design hierarchy from reusable parts. Structural composition like this is how real chips are built out of smaller, tested blocks.
Foundation14 min readVHDLInstantiationStructuralHierarchyComponentsReuse
1. Engineering intuition — placing and wiring sub-blocks
A large design is not one flat block of logic; it is a hierarchy of smaller blocks — an ALU here, a FIFO there, a controller wiring them together. Building that in VHDL is structural description: you take an existing entity (a defined block with ports) and instantiate it — place a copy inside your architecture and connect its ports to your signals. It is the textual equivalent of dropping a chip onto a board and soldering its pins to nets.
2. Formal explanation — two instantiation styles
An instantiation is a labelled concurrent statement. There are two ways to write it:
-- (1) DIRECT ENTITY INSTANTIATION (modern, preferred) — no component declaration needed:
u_add : entity work.adder(rtl) -- library.entity(architecture)
port map ( a => x, b => y, sum => s );
-- (2) COMPONENT INSTANTIATION (older) — declare a matching component, then instantiate:
-- in the declarative region:
component adder
port ( a, b : in std_logic_vector(7 downto 0); sum : out std_logic_vector(7 downto 0) );
end component;
-- in the body:
u_add2 : adder port map ( a => x, b => y, sum => s );Both create an instance labelled u_add/u_add2. Direct entity instantiation references the
entity (and optionally a specific architecture) straight from the library, so there is no separate
component to declare and keep in sync. The component style needs the redundant component
declaration and a binding (default or via configuration). Prefer direct entity instantiation in new
code.
3. Production-quality RTL — a small structural hierarchy
library ieee; use ieee.std_logic_1164.all;
architecture struct of datapath is
signal stage : std_logic_vector(7 downto 0); -- the wire between the two instances
begin
-- instance 1: an adder sub-block
u_add : entity work.adder
port map ( a => in_a, b => in_b, sum => stage );
-- instance 2: a register sub-block, fed by the adder's output net
u_reg : entity work.reg8
port map ( clk => clk, d => stage, q => result );
end architecture;What hardware does this become? Two sub-blocks — an adder and an 8-bit register — placed in the
hierarchy and wired by the stage net. They run concurrently (lesson 4.1): the adder continuously
computes, the register captures on its clock. The struct architecture is a netlist of
instances, not a sequence of operations.
4. Hardware interpretation — instances are the hierarchy
5. Simulation / elaboration interpretation — instances are built, not run
This lesson is about structure, so the meaningful "timeline" is elaboration, not a waveform: before simulation starts, the elaborator expands each instantiation, creating the sub-block's signals and processes and binding its ports to the parent's nets. Once elaborated, every instance participates in the same event-driven engine (lesson 3.4) concurrently. Because the behaviour over time is just the composed behaviour of the sub-blocks, a structural diagram (above) communicates the design far better than a waveform would — there is no new timing concept here, only connectivity.
6. Debugging example — the unbound / mismatched instance
Expected: an instantiated sub-block works. Observed: elaboration error ("cannot find
entity/architecture" or "formal/actual mismatch"), or — with the component style — the design
elaborates but the instance behaves as an empty black box. Root cause: with direct entity
instantiation, the referenced entity/architecture name or library is wrong; with the component
style, the component declaration does not match the real entity's ports (a name/width drift), so
binding fails or defaults to nothing. Fix: use direct entity instantiation to eliminate the
duplicate component declaration, or keep the component declaration exactly in sync with the
entity. Engineering takeaway: most instantiation bugs are binding bugs — the instance cannot
find or match its definition; direct entity instantiation removes the most common source by deleting
the redundant declaration.
-- RISK (component style): the component drifts from the real entity (width changed to 16).
component adder
port ( a, b : in std_logic_vector(7 downto 0); sum : out std_logic_vector(7 downto 0) );
end component; -- now mismatches the real 16-bit adder → bind error
-- FIX: reference the entity directly; no separate declaration to drift.
u_add : entity work.adder port map ( a => x, b => y, sum => s );7. Common mistakes & what to watch for
- Keeping stale
componentdeclarations. They drift from the entity's real ports. Prefer direct entity instantiation, which has no declaration to maintain. - Wrong library/architecture reference.
entity work.foo(rtl)must name an entity and architecture that exist in the working library. - Treating instances as sequential. Instances are concurrent (lesson 4.1); their order in the architecture is irrelevant.
- Forgetting an instance label. Every instantiation needs a unique label (
u_add :); it names the instance in the hierarchy and in simulation. - Leaving outputs unconnected by accident. Unconnected actuals can leave nets undriven (
'U'); connect every port you rely on (open is allowed only deliberately).
8. Engineering insight
Instantiation is where VHDL becomes a hierarchy rather than a single block, and that hierarchy is the backbone of reuse: a well-defined entity (a clean port contract) can be placed anywhere, any number of times, and verified once. The shift from component declarations to direct entity instantiation is not cosmetic — it removes a whole class of drift bugs by deleting the duplicate interface description. Think structurally: design small entities with crisp interfaces, then build larger systems by placing and wiring them, exactly as you would compose a board from chips. The next lesson makes the "wiring" precise.
9. Summary
A component instantiation is a labelled concurrent statement that places a copy of an entity into an
architecture and connects it, building the design hierarchy. Direct entity instantiation
(entity work.foo) is the modern, drift-free style; the older component form needs a separate,
must-stay-in-sync component declaration. Each instance is independent hardware running in parallel,
wired to others by signals, and expanded at elaboration.
10. Learning continuity
You can now place a sub-block — but the connection is the port map, and it deserves its own
treatment. The next lesson, Port Maps — Named and Positional, covers exactly how an instance's
ports bind to your signals: the safe named style (a => x) versus the terse positional style, how
to leave a port open, and how mismatches here cause the binding bugs you just saw.