VHDL · Chapter 4.1 · Concurrent Statements
The Concurrent Execution Model
Software runs one line after another. Hardware does not, because every gate is always active, all at once, and VHDL models exactly that. An architecture body is a set of concurrent statements that execute simultaneously, so their textual order is irrelevant: swapping two lines changes nothing, because they were never running in sequence. Each statement, whether a concurrent assignment, a process, a component instance, or a generate, is an event-driven block that re-evaluates only when one of its inputs changes, and the delta-cycle engine you met earlier coordinates them into a deterministic result. Internalizing this, thinking parallel rather than sequential, is the dividing line between writing software-shaped VHDL that mis-synthesizes and writing real hardware.
Foundation14 min readVHDLConcurrencyConcurrent StatementsEvent-DrivenRTLDataflow
1. Engineering intuition — hardware is always on
Picture a board of gates: every gate continuously watches its inputs and drives its output. None of them "waits its turn." A VHDL architecture describes precisely that — a collection of always-active blocks running in parallel. There is no program counter stepping through the statements; they all exist and react at the same time. The moment you accept that the architecture is a netlist of concurrent blocks, not a sequence of instructions, VHDL stops surprising you.
2. Formal explanation — concurrent statements and order independence
Everything written directly in an architecture body (between begin and end) is a concurrent
statement. The major kinds are: concurrent signal assignments (lesson 4.2), processes,
component/entity instantiations, and generate statements. They all execute concurrently, and
crucially their order is immaterial:
architecture rtl of logic is
begin
y <= a and b; -- these three are concurrent;
z <= y or c; -- writing them in ANY order
w <= a xor c; -- produces the identical hardware and behaviour
end architecture;
-- Reordering the lines changes nothing: each is an always-active block,
-- re-evaluated when its own inputs change — not when the line "executes".Each statement behaves as if it has an implicit sensitivity to the signals it reads: when any of those change, it re-evaluates. A process makes this explicit with its sensitivity list; a concurrent assignment has it implicitly.
3. Production-quality RTL — parallel blocks sharing nets
A real architecture is several concurrent blocks wired together by shared signals — a small netlist:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
architecture rtl of unit is
signal sum : unsigned(7 downto 0);
begin
-- concurrent assignment: combinational adder (always live)
sum <= unsigned(a) + unsigned(b);
-- process: a register (event-driven on clk)
reg : process (clk) begin
if rising_edge(clk) then q <= std_logic_vector(sum); end if;
end process;
-- component instance: a sub-block, also running concurrently
u_chk : entity work.range_check port map (clk => clk, v => sum, bad => overflow);
end architecture;What hardware does this become? Three pieces of always-active hardware — an adder, a register,
and a checker sub-block — coexisting and communicating through sum. They are not steps; they are
parts, all live simultaneously.
4. Hardware interpretation — concurrency is the parallelism of silicon
5. Simulation interpretation — event-driven scheduling
A simulator cannot literally run everything at the same instant, so it emulates concurrency with the event-driven engine from lesson 3.4: a signal change is an event that wakes every concurrent statement sensitive to that signal; each evaluates and schedules its own outputs one delta later; the deltas settle; then real time advances. The result is identical for any statement order, which is what makes concurrency deterministic.
Two concurrent statements react independently and simultaneously to their own inputs
8 cycles6. Debugging example — the sequential-thinking bug
Expected: a beginner writes three concurrent assignments expecting them to "run in order" and share a temporary. Observed: the result is not what sequential reading predicts — values look "off by a delta" or a temporary never carries the intended intermediate. Root cause: the statements are concurrent, not sequential; the "temporary" signal updates one delta later and is read by the others using its old value, and reordering would not help because order is irrelevant. Fix: if step-by-step computation with immediate intermediate values is needed, do it inside a single process using variables (lesson 3.1); keep concurrent statements for genuinely parallel logic. Engineering takeaway: when concurrent results defy a top-to-bottom reading, the bug is the sequential mental model — move ordered computation into a process, or accept the delta-true parallel behaviour.
-- WRONG expectation: 'tmp' is computed then immediately reused, as if sequential.
tmp <= a + b; -- concurrent: tmp updates one delta later
y <= tmp * 2; -- reads the OLD tmp → not "this line's" value
-- FIX: for ordered, immediate intermediates, use a process + variable:
process (a, b)
variable t : unsigned(7 downto 0);
begin
t := unsigned(a) + unsigned(b); -- immediate
y <= std_logic_vector(t * 2); -- uses the new t
end process;7. Common mistakes & what to watch for
- Assuming statement order matters. Concurrent statements are order-independent; reordering them changes nothing.
- Expecting sequential, immediate computation between concurrent statements. A signal updates one delta later; chained concurrent assignments are not step-by-step. Use a process+variables for that.
- Treating an architecture like a function body. It is a netlist of parallel blocks, not a sequence of instructions.
- Forgetting each block is event-driven. A concurrent statement re-evaluates only when its inputs change — define those inputs (implicitly via the expression, or a complete sensitivity list in a process).
- Hiding ordered logic in many concurrent lines. If the algorithm is inherently sequential, express it in one process, not a fan of concurrent assignments.
8. Engineering insight
Concurrency is not a VHDL feature bolted onto a programming language — it is the language admitting what hardware is: massively parallel, always-active, communicating through nets. The payoff of embracing it is that your code's structure mirrors the silicon's structure, so it synthesises predictably and reads like a schematic. The event-driven engine guarantees this parallel model is deterministic, so you never reason about statement order — only about which signals feed which blocks. Every later construct (processes, combinational and sequential logic, FSMs, structural hierarchy) is a kind of concurrent statement; this model is the frame they all sit in.
9. Summary
A VHDL architecture is a set of concurrent statements that execute simultaneously, so their textual order is irrelevant. Each — concurrent assignment, process, component, generate — is an event-driven block re-evaluated when its inputs change, coordinated deterministically by the delta-cycle engine. This is the direct model of always-active hardware, and thinking sequentially about it is the classic source of RTL bugs.
10. Learning continuity
Now that you see the architecture as parallel, event-driven blocks, the next step is the simplest
such block. Concurrent Signal Assignment examines target <= expression; on its own — a
continuously-live driver equivalent to a process sensitive to everything it reads, and the purest
form of combinational logic — before the module adds structure with conditional (when/else) and
selected (with/select) assignments, instantiation, and generate.