← All guides

Variables vs signals in VHDL

VHDL gives you two kinds of object you can assign inside a process, and the difference between them is the difference between two circuits. Signals (<=) don’t take their new value until the process suspends; every read during the process sees the old value. Variables (:=) update immediately, like a line of software; the next statement sees the new value.

If you know Verilog, this is the blocking vs nonblocking story told with types instead of operators: signal assignment behaves like <=, variable assignment like =. The trap just moves: in Verilog you pick the wrong operator, in VHDL you pick the wrong kind of object.

The demonstration: a flip-flop disappears

With signals, this clocked process is a two-stage shift register. The second line reads stage1’s old value, because the new one hasn’t landed yet:

process (clk) is
begin
  if rising_edge(clk) then
    stage1 <= d;       -- signals: stage2 reads the OLD stage1
    stage2 <= stage1;  -- two flip-flops, a real shift register
  end if;
end process;

Live, and editable. Count the registers: two.

Interactive schematic by RapidRTL

Now the same shape written with a variable in the middle. The variable updates immediately, so stage2 reads the new value, which is just d. The middle stage isn’t storage any more, and synthesis agrees. Count the registers: one.

Interactive schematic by RapidRTL

A flip-flop vanished because of one token. That’s the entire subject, made visible: a variable read after it’s written in the same clock edge is a wire, not a register. Whether that’s a bug or exactly what you wanted depends on which circuit you meant to describe.

When variables are the right tool

The example above makes variables look like a hazard, but the immediate update is genuinely useful wherever you’re computing, not storing:

Intermediate values inside one edge. A sum built in steps, a parity, a byte-swap: things that exist only as scaffolding between the inputs and the one register that stores the result.

process (clk) is
  variable parity : std_logic;
begin
  if rising_edge(clk) then
    parity := '0';
    for i in data'range loop
      parity := parity xor data(i);  -- accumulates across the loop
    end loop;
    parity_reg <= parity;            -- one register, at the end
  end if;
end process;

That loop needs immediate update: each iteration must see the previous one’s result. Written with a signal, every iteration would read the same old value, the loop would collapse to a single XOR, and simulation would disagree with your intent. When you accumulate, you want variable semantics; when you store across clock edges, you want a signal.

Keeping scope tight. A variable exists only inside its process, which is the honest scope for scaffolding. (VHDL does have shared variables across processes; treat them as a specialist tool you’ll know when you need, which is approximately never in synthesizable code.)

The rules

Why simulation makes it feel fine

Both versions of the shift register simulate cleanly; nothing warns you that a register evaporated. The mismatch only becomes visible when you look at the netlist, which is exactly what the two schematics above are. It’s the same moral as the latch guide: simulation checks what the code does, synthesis reveals what the code is, and the habit worth building is looking at both.