VHDL · Chapter 3.1 · Signals & Hardware Behavior
Signals vs Variables
If you learn one thing about how VHDL behaves, learn this. A signal is a wire: assigning it does not change its value immediately, because the new value is scheduled and arrives after an infinitesimal delta delay, so if you read the signal again in the same process step you still see its old value. A variable is local scratch storage that updates immediately, like a variable in software. This is not a stylistic preference. It is the simulation model, and it decides whether your logic reads stale or fresh data, whether a swap works, and whether you get the register you intended. This lesson makes the delta-delay behaviour concrete and ties each construct to real hardware such as wires, registers, and intermediate computation.
Foundation15 min readVHDLSignalsVariablesDelta DelayProcessesSemantics
1. Intuition — scheduled wire vs immediate scratch
A signal models a wire. When you write s <= value, you are not storing a value, you are
scheduling the wire to carry it. The update happens after a delta delay — an
infinitesimal step that occurs before simulation time advances. Until then, s keeps its
current value.
A variable is local to a process and behaves like an ordinary programming variable. When
you write v := value, v changes immediately and in place. The very next line sees the new
value.
signal s : integer; -- a wire: '<=' schedules, updates after a delta
variable v : integer; -- process-local: ':=' updates immediatelyThat single difference — scheduled versus immediate — explains almost every "why did my VHDL do that?" moment for the rest of the language.
2. The behaviour, drawn
3. The same code, two results
Put the two side by side in a process. With a signal, the second line reads the old value because the first assignment has only been scheduled; with a variable, the second line reads the new value:
-- SIGNAL version: 'temp_s' is still its OLD value when read on the next line
process (clk)
begin
if rising_edge(clk) then
temp_s <= a + b; -- scheduled; temp_s not updated yet
result <= temp_s * 2; -- uses the OLD temp_s (a full cycle behind)
end if;
end process;
-- VARIABLE version: 'temp_v' updates immediately, so the multiply sees the NEW sum
process (clk)
variable temp_v : unsigned(7 downto 0);
begin
if rising_edge(clk) then
temp_v := a + b; -- immediate
result <= temp_v * 2; -- uses the NEW temp_v (same cycle)
end if;
end process;(Both use numeric_std arithmetic on unsigned, from lesson 2.8.) The signal version inserts
an extra cycle of latency you did not ask for; the variable version computes the intended value
in one cycle. Neither is "wrong" — but you must pick the one that matches your intent.
4. The classic swap — proof the lag is real
Because signal updates are scheduled (and read using old values), two signals can be swapped in one clocked process without a temporary — something impossible with immediate-update variables:
Signal swap: a and b exchange values every clock because both reads use the OLD values
8 cyclesprocess (clk)
begin
if rising_edge(clk) then
a <= b; -- both RHS read the OLD a and b (scheduled updates)
b <= a; -- so this is a true swap, no temporary needed
end if;
end process;5. Hardware interpretation
The scheduling difference maps cleanly onto silicon:
- A signal models a real net. Assigned in a clocked process, it becomes a register (its value is held and updated on the clock edge); assigned in a combinational context, it is a wire driven by logic.
- A variable models intermediate computation inside one process. Depending on how its value is used, it synthesises to combinational logic (a wire computed and consumed in the same cycle) or, if its value is needed across clock edges, to a register. It is not a stored net of its own — it is the algebra between nets.
The guidance follows directly: use signals for anything that crosses process boundaries, is observed elsewhere, or must be a registered output; use variables for local, step-by-step computation you want to read back immediately within the same process.
6. Debugging example — the accidental extra cycle
A common bug: a pipeline that is "one cycle late" because intermediate results were held in signals and read before they updated.
signal stage1, stage2 : unsigned(7 downto 0);
process (clk)
begin
if rising_edge(clk) then
stage1 <= data_in; -- scheduled
stage2 <= stage1 + 1; -- reads OLD stage1 → effectively data_in delayed TWICE
end if;
end process;
-- Result: stage2 reflects data_in from two cycles ago, not one. If you intended a
-- single-cycle combine, use a variable for stage1 so the '+ 1' sees this cycle's data.The fix depends on intent: if you want two pipeline registers, this is correct; if you wanted
the increment to use this cycle's data_in, compute it with a variable (immediate) and
register only the final result. The bug is reading a freshly-scheduled signal expecting the new
value.
7. Common mistakes & what to watch for
- Expecting a signal to update immediately. It does not; a same-step read returns the old value. Use a variable when you need the new value within the process.
- Using a variable where you need cross-process visibility. Variables are process-local and invisible outside; a wire that other logic reads must be a signal.
- Mixing up the operators.
<=is for signals (and is also "less-than-or-equal" in an expression — context decides);:=is for variables, constants, and initial values. Using:=on a signal or<=on a variable does not compile. - Assuming variables are never registers. A variable whose value must survive across clock edges infers a register; variables are not automatically "just wires."
- Declaring variables outside a process. A variable is declared in the process's declarative region (or is a shared variable, which you should avoid); signals are declared in the architecture.
8. Engineering insight
Signals and variables are the simulation model made into syntax. A signal is a scheduled net: its delta-delayed update is precisely what lets independent processes communicate consistently and lets clocked assignments behave like real flip-flops. A variable is immediate local state: it lets you write natural sequential algorithms inside one process. Choosing between them is a hardware decision disguised as a coding one — signal for wires, registers, and inter-process data; variable for intra-process computation. Internalise the one-delta lag and the rest of VHDL's behaviour — swaps, pipelines, multiple drivers — stops being surprising.
9. Summary & next step
A signal (<=) schedules its update and changes only after a delta delay, so a same-step read
sees the old value; a variable (:=) updates immediately. Signals model wires and registers and
cross process boundaries; variables model local computation. The difference governs latency,
swaps, and whether you read stale or fresh data — it is the simulation model, not a style.
You now know that a signal updates by scheduling. The next lesson opens up exactly how that scheduling works: signal assignment — the driver a signal projects values onto, why the last assignment in a process wins, and how the delta-cycle update connects to multiple drivers and resolution.