Skip to content

VHDL · Chapter 11.8 · Functions and Procedures

Pure vs Impure Functions

VHDL functions come in two flavours, and the distinction matters for both correctness and synthesis. A pure function, which is the default, computes its result only from its parameters. It may not read signals or shared variables from the enclosing scope, so the same inputs always produce the same output. That determinism is exactly what combinational logic is, which is why synthesizable functions must be pure. An impure function, declared with the impure keyword, may reach outside and read outer signals or shared variables, so its result can change for identical arguments. That is invaluable in testbenches, for sampling a signal, reading a timestamp, or logging through shared state, but it is not synthesizable RTL. This closing lesson explains the two kinds, why pure is the default, and what impure is genuinely for.

Foundation13 min readVHDLFunctionsPureImpureSynthesisTestbench

1. Engineering intuition — does the answer depend only on the inputs?

Ask a single question of any function: if I call it twice with the same arguments, do I always get the same result? If yes, it is pure — its answer is a function of its inputs alone, exactly like a block of combinational gates whose output depends only on its present inputs. If the answer can differ — because the function peeked at some outer signal or counter that changed between calls — it is impure. Hardware can only be the first kind: gates have no way to "read the current simulation time" or a global. So pure-vs-impure is really the line between what can be a circuit and what can only be simulation behaviour.

2. Formal explanation — pure (default) vs impure

pure_vs_impure.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- PURE (default): result depends ONLY on parameters. No reading of outer signals/shared variables.
-- Deterministic → same inputs, same output → synthesizable combinational logic.
function add1 (x : integer) return integer is
begin
  return x + 1;                 -- depends only on x
end function;
 
-- IMPURE: 'impure' keyword; may READ enclosing signals / shared variables.
-- Result can differ for the SAME arguments → simulation/testbench only.
impure function sample_bus return std_logic_vector is
begin
  return bus_sig;               -- reads an OUTER signal → not a function of parameters → impure
end function;

A pure function (the default — you may write pure but rarely do) is forbidden from reading signals or shared variables outside its parameters, guaranteeing determinism. An impure function is explicitly marked and is permitted to read outer state, so its return value is not determined by its arguments alone — which is why it is a simulation construct, not hardware.

3. Production usage — pure for RTL, impure for testbenches

where_each_belongs.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- RTL: pure functions only — deterministic, synthesizable.
y <= std_logic_vector(to_unsigned(clog2(DEPTH), 4));    -- clog2 is pure → folds to logic/constant
 
-- TESTBENCH: impure functions read outer state for stimulus/checking.
impure function next_stimulus return std_logic_vector is
begin
  return scoreboard_data;        -- reads shared TB state → impure, expected here
end function;
-- impure functions that sample a signal or a free-running counter are common in verification,
-- where "the value right now" is exactly what you want.

What hardware does this become? A pure function inlines to deterministic combinational logic (an adder, a comparator, a constant fold) — clog2(DEPTH) becomes logic or a constant. An impure function has no hardware meaning: reading "the current value of an outer signal" inside a function is a simulation behaviour, not a gate, so impure functions are confined to testbenches and models. The keyword is the contract: pure (the default) promises synthesizability; impure declares a simulation-only reach into outer state.

4. Structural interpretation — inputs only vs reading outer state

pure function depends only on parameters; impure function also reads outer statereads (TB only)parametersthe inputsPURE functionresult = f(params only) →synthesizableIMPURE functionalso reads outersignals/shared varsouter signals /shared varsenclosing state12
The pure/impure distinction. A pure function (the default) computes its result only from its parameters — no outer signals or shared variables — so identical inputs always give an identical output; that determinism is combinational logic, and it synthesizes. An impure function (marked impure) may additionally read enclosing signals or shared variables, so its result can vary for the same arguments; that has no gate equivalent and belongs to testbenches and models. Synthesizable RTL functions must be pure. This is a dependency structure — what the result depends on — not a timing waveform.

5. Why this is structural, not timing

Purity is a property of what a result depends on — parameters only (pure) versus parameters plus outer state (impure) — which is a dependency structure, so the diagram above captures it, not a waveform. A pure function's behaviour is just the deterministic combinational logic it inlines to (already seen in waveform form in the combinational module). The genuinely time-varying aspect belongs to impure functions in simulation — reading "the value now" — but that is verification behaviour, not synthesizable hardware, so it has no RTL waveform of its own.

6. Debugging example — the accidental impurity

Expected: an RTL helper function synthesizes. Observed: a synthesis error (or a warning that a function reads a signal / must be impure), or non-deterministic simulation where the same inputs give different results. Root cause: the function read an outer signal or shared variable instead of taking it as a parameter — so it is (or must be declared) impure, which is not synthesizable and not deterministic. Fix: pass every value the function needs as a parameter so the result depends only on its inputs, keeping it pure; reserve impure functions for testbenches where reading outer state is the intent. Engineering takeaway: if an RTL function needs a value, pass it in — reading outer signals makes the function impure (simulation-only); pure functions take everything through their parameters.

pass_it_in.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: reads an outer signal → impure, not synthesizable, not deterministic.
-- impure function g return std_logic is begin return mode_sig; end function;
-- FIX: take it as a parameter → pure, synthesizable.
function g (mode : std_logic) return std_logic is begin return mode; end function;

7. Common mistakes & what to watch for

  • Reading outer signals in an RTL function. That forces impure and breaks synthesis; pass the value as a parameter to stay pure.
  • Marking a function impure unnecessarily. Only testbench functions that must read outer state need it; RTL functions should be pure (the default).
  • Expecting determinism from an impure function. Its result can vary for the same arguments — that is the point in verification, but never rely on it for repeatable logic.
  • Confusing pure with side-effect-free. Pure also means it does not read outer state, not just that it does not write — functions cannot drive signals anyway.
  • Using impure functions to fake state in RTL. Hold state in registers/signals; do not smuggle it through impurity.

8. Engineering insight & continuity

A pure function depends only on its parameters — deterministic, the default, and the only kind that synthesizes (combinational logic); an impure function may read outer signals or shared variables, trading determinism for the ability to sample live state in testbenches. The rule for RTL is simple: pass everything in, stay pure. This completes Module 11: Functions and Procedures — you can now package reusable computation correctly, know what synthesizes, and keep RTL helpers pure. The curriculum next turns to the other great reuse mechanism, parameterization: Module 12 — Generics, where generic parameters let one entity serve many widths, depths, and configurations.