VHDL · Chapter 5.9 · Sequential Statements & Processes
Sequential Signal Assignment and Last-Assignment-Wins
A signal assignment inside a process is scheduled, not immediate, and a process contributes exactly one driver to each signal it assigns. The consequence, and the rule that powers most clean RTL, is last-assignment-wins: if a process assigns the same signal several times in one run, every assignment targets the same pending update, so only the last one executed survives. That is exactly why the default-then-override idiom works, where you assign a safe default first, override it conditionally, and get latch-free combinational logic by construction. This capstone lesson consolidates sequential signal assignment, contrasts it once more with a variable's immediate update, explains what one driver per process means, and ties every construct in the module back to the underlying execution model.
Foundation14 min readVHDLSignal AssignmentLast-Assignment-WinsDefault OverrideLatch-freeSequential
1. Engineering intuition — the last word wins
Inside a process, writing s <= x does not change s then and there; it schedules s to become x
after the process suspends. So if you write s <= a and later s <= b in the same run, you are not
making two changes — you are setting, then resetting, the one value s is scheduled to take. The
later write overwrites the earlier one, and only the last survives. This "last word wins" is not a
quirk to memorise; it is the direct result of scheduling, and it is the mechanism behind the most
important combinational idiom in VHDL.
2. Formal explanation — scheduled, one driver, last-wins
process (sel, a, b)
begin
y <= a; -- schedule y := a ...
if sel = '1' then
y <= b; -- ... overwrite: schedule y := b (only this survives when sel='1')
end if;
end process;
-- Net effect: y = b when sel='1', else y = a. The first assignment is a DEFAULT.A sequential <= schedules a transaction on the signal's single driver for this process. Within one
wake-up, multiple assignments to the same signal all target the same delta update, so the last one
executed wins; the earlier ones never appear. A process is one driver per signal — if two
different processes assign the same signal, that is two drivers and resolution (lesson 3.6), not
last-wins. Last-assignment-wins is strictly within one process run.
3. Production RTL — default-then-override everywhere
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- (A) Combinational mux, latch-free by default-then-override.
mux : process (all)
begin
y <= b; -- default
if sel = '1' then y <= a; end if; -- override (last wins) → 2:1 mux, no latch
end process;
-- (B) case with a default: every output assigned on every path → latch-free.
dec : process (all)
begin
code <= "0000"; -- default covers all selectors
case op is
when "00" => code <= "0001";
when "01" => code <= "0010";
when "10" => code <= "0100";
when others => null; -- default already drove 'code'
end case;
end process;
-- (C) Clocked register: a single sequential assignment per signal.
reg : process (clk)
begin
if rising_edge(clk) then
q <= d; -- one scheduled update per edge
end if;
end process;What hardware does this become? (A) a 2:1 mux with no latch, because the default assigns y on
every path; (B) a decoder whose default guarantees code is always driven; (C) a flip-flop. In each
case last-assignment-wins lets the default sit harmlessly under the specific overrides.
4. Hardware interpretation — last-wins and the latch-free idiom
5. Simulation interpretation — scheduled, then applied
By the anchor's model, each <= in the run updates the pending scheduled value of the signal; the
signal itself does not change until the process suspends, one delta later. So a signal read after
being assigned in the same run still returns its old value — which is why intermediates use variables
(immediate), and why last-assignment-wins is about the final scheduled value, not intermediate ones.
Default-then-override: y = a when sel=1 (override), else the default b
8 cycles6. Debugging example — relying on an intermediate signal value
Expected: a two-step computation in a process produces a same-run result. Observed: the result
is wrong or a cycle late. Root cause: the code assigned an intermediate signal and then read it
later in the same run, expecting the new value — but a sequential <= is scheduled, so the read
returned the old value (and, registered, added a cycle). Last-assignment-wins governs the final
value, not mid-run reads. Fix: use a variable for the intermediate (immediate update). Engineering
takeaway: within a run, signals never reflect a just-made assignment; use variables for intermediates,
and reserve signal <= for the values you want to commit at the end of the run.
-- BUG: read an intermediate SIGNAL after assigning it → old value (scheduled).
process (all) begin
t_s <= unsigned(a) + unsigned(b); -- scheduled
y <= std_logic_vector(t_s + 1); -- reads OLD t_s
end process;
-- FIX: VARIABLE intermediate (immediate), signal only for the committed output.
process (all)
variable t_v : unsigned(7 downto 0);
begin
t_v := unsigned(a) + unsigned(b); -- immediate
y <= std_logic_vector(t_v + 1); -- uses NEW t_v
end process;7. Common mistakes & what to watch for
- Expecting an intermediate signal assignment to take effect mid-run. It is scheduled; reads return the old value. Use a variable for immediate intermediates.
- Omitting the default in a combinational process. Without a default (or full coverage) some path leaves the signal unassigned → latch. Default-then-override fixes it.
- Assuming last-wins across processes. Last-assignment-wins is within one process; two processes driving one signal is multiple drivers and resolution (lesson 3.6) — usually an error.
- Adding an else that clears a register. In a clocked process, a single
<=under the edge guard holds when not assigned; an unintended else can reset it. - Confusing
<=and:=.<=schedules a signal;:=updates a variable immediately.
8. Engineering insight
Last-assignment-wins is the small rule that makes large RTL clean. Because a process schedules one
final value per signal, you can lay down a safe default and override it conditionally, guaranteeing
every output is driven on every path — latch-free by construction, and far more readable than
enumerating every case. It also clarifies the division of labour inside a process: variables for
immediate intermediate computation, signals for the values you commit at the end of the run, exactly
one driver per signal per process. Master this and the whole sequential world — if, case, loops,
variables, wait — composes into predictable, synthesizable hardware.
9. Summary
A sequential signal assignment (<=) schedules the signal's single pending update for this process;
within one run the last assignment wins, since all target the same delta update. This powers the
default-then-override idiom for latch-free combinational logic. It differs from a variable's immediate
update, applies only within one process (cross-process assignment is multiple-driver resolution), and
follows directly from the execution model.
10. Learning continuity
This capstone completes Module 5 — Sequential Statements and Processes: the execution model (the
anchor), if and case decisions, loops, variables, wait, and now sequential signal assignment with
last-assignment-wins. With processes and sequential statements fully in hand, the curriculum turns to
applying them: Module 6 — Combinational Logic Design uses the combinational process, default
assignments, and latch avoidance you have just learned to build muxes, decoders, and arithmetic
cleanly, before Module 7 takes the clocked process into registers, counters, and full sequential logic.