VHDL · Chapter 5.7 · Sequential Statements & Processes
Variables Inside Processes
A variable is the process's scratchpad, declared in the process's own region and updated immediately so the very next line sees the new value. Most of the time a variable is just combinational intermediate wiring, a place to hold a partial result you compute and consume in the same run. But the same construct becomes memory the moment its value is needed before it is assigned in a run, because reading before writing forces the variable to remember its value from last time and infers a register or latch. This lesson pins down the immediate-update semantics, the canonical accumulator and intermediate patterns, exactly when a variable turns into storage, and why shared variables are a trap to avoid.
Foundation14 min readVHDLVariablesImmediate UpdateRegistersCombinationalSequential
1. Engineering intuition — a scratchpad you read back instantly
Think of a variable as local scratch paper inside a process. You write a value on it and it is there immediately — the next statement reads exactly what you just wrote. That immediacy is the whole point: it lets you build a result in steps within one wake-up (compute a sum, then use it; accumulate across loop iterations) without the one-delta lag a signal would impose. Most variables are exactly this — temporary wiring that exists only to carry a partial result from one line to the next.
2. Formal explanation — scope, immediacy, and when it becomes storage
process (a, b)
variable t : unsigned(7 downto 0); -- declared in the process region; process-local
begin
t := unsigned(a) + unsigned(b); -- ':=' updates t IMMEDIATELY
y <= std_logic_vector(t); -- the next line sees the new t
end process;A variable is declared in the process's declarative region and is visible only inside that
process. Assignment uses := and takes effect immediately. The key synthesis rule: a variable
that is written before it is read in every run is combinational (intermediate wiring); a
variable that is read before it is written in a run must remember its value from last time — so it
infers storage (a register in a clocked process, a latch in a combinational one).
3. Production RTL — intermediate, accumulator, and the storage case
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- (A) Combinational intermediate: write-then-read → pure wiring, no storage.
comb : process (all)
variable s : unsigned(8 downto 0);
begin
s := resize(unsigned(a), 9) + resize(unsigned(b), 9); -- compute
carry <= s(8); -- consume same run
sum <= std_logic_vector(s(7 downto 0));
end process;
-- (B) Registered accumulator: variable is intermediate; the SIGNAL holds state.
acc : process (clk)
variable t : unsigned(15 downto 0);
begin
if rising_edge(clk) then
t := unsigned(acc_q) + unsigned(a); -- read acc_q (a signal), compute new total
acc_q <= std_logic_vector(t); -- the register (signal) stores it
end if;
end process;
-- (C) Read-before-write → the variable itself becomes a register (use deliberately).
running : process (clk)
variable count : unsigned(7 downto 0) := (others => '0');
begin
if rising_edge(clk) then
count := count + 1; -- reads OLD count, then writes → count is a REGISTER
q <= std_logic_vector(count);
end if;
end process;What hardware does this become? (A) s is combinational wiring feeding sum/carry — no storage;
(B) t is combinational intermediate logic, and the signal acc_q is the accumulator register; (C)
count is read before written each cycle, so it synthesises to an 8-bit register — a deliberate use of
a variable as state. The pattern you write decides wire vs register.
4. Hardware interpretation — wire or register, decided by read-before-write
5. Simulation interpretation — immediate, versus a signal's delta
By the anchor's model, a variable updates in place the instant you assign it, so later statements in the same wake-up read the new value; a signal assigned alongside is only scheduled and still reads its old value until the process suspends. That difference is exactly why variables are used for intermediate computation and accumulation.
Variable intermediate vs signal intermediate in a clocked datapath
8 cycles6. Debugging example — the accidental latch from a variable
Expected: combinational logic. Observed: synthesis infers a latch on a variable-derived output. Root cause: in a combinational process the variable was read before it was written on some path — its value had to be remembered from a previous run, which is memory. Fix: assign the variable on every path before reading it (a default/initial assignment at the top of the run), so it is always write-before-read. Engineering takeaway: a latch tied to a variable means read-before-write in a combinational process; initialise the variable at the top of each run so it is always fresh.
-- BUG: 'm' read on a path where it was not assigned this run → inferred latch.
process (all)
variable m : unsigned(7 downto 0);
begin
if sel = '1' then m := unsigned(a); end if; -- only assigned when sel='1'
y <= std_logic_vector(m); -- reads m even when sel='0' → stale → latch
end process;
-- FIX: assign m on every run before use.
process (all)
variable m : unsigned(7 downto 0);
begin
m := unsigned(b); -- default this run (write before read)
if sel = '1' then m := unsigned(a); end if;
y <= std_logic_vector(m); -- always fresh → combinational
end process;7. Common mistakes & what to watch for
- Read-before-write in a combinational process. Infers a latch; assign the variable at the top of each run before reading it.
- Expecting cross-process visibility. Variables are process-local; a value other processes must see is a signal.
- Shared variables. A
shared variablecrosses processes and invites races; avoid it for design (use signals) and use it only in carefully-scoped verification code. - Confusing
:=and<=.:=is immediate (variables/constants);<=is scheduled (signals). Using the wrong one is a compile error or a timing bug. - Assuming a variable never stores. Read-before-write in a clocked process makes it a register — fine when intended, a surprise when not.
8. Engineering insight
Variables make sequential, step-by-step computation natural inside a process — accumulate, transform, then assign the result to a signal — and because they update immediately they avoid the extra-cycle latency a chain of signals would add. The one rule to internalise is access order decides hardware: write-before-read is a wire, read-before-write is a register/latch. Use variables freely for intermediates and accumulators, always assign them before reading in combinational logic, keep the observable state in signals, and steer clear of shared variables. That keeps variables a precise, predictable tool rather than a source of mysterious storage.
9. Summary
A variable is process-local storage updated immediately with :=, most often combinational intermediate
wiring. Access order decides the hardware: written-before-read is a wire; read-before-write infers a
register (clocked) or latch (combinational). Use variables for same-run intermediates and accumulators,
keep observable state in signals, assign before reading in combinational logic, and avoid shared
variables.
10. Learning continuity
Variables are one way to trigger and structure a process's work via the sensitivity-list style. The
next lesson introduces the other triggering mechanism: The wait Statement, which suspends a
process explicitly. You will see the testbench forms (wait for, wait until) and the one
synthesizable idiom, wait until rising_edge(clk), that expresses a register without a sensitivity
list — closing out how processes are driven before the module's capstone on sequential signal
assignment.