Skip to content

VHDL · Chapter 6.4 · Combinational Logic Design

Default Assignments and Latch Avoidance

The previous lesson showed how an unassigned path infers a latch. This lesson is the cure, turned into a habit you apply to every combinational process: the default-then-override pattern. At the very top of the process, assign every output a safe default value, then let your if and case logic conditionally override it. Because the default runs on every path, every output is always driven, so a latch is impossible by construction no matter how complex the conditional logic gets. This scales to multi-output blocks, case decodes, and FSM output logic far better than trying to assign every output in every branch. The one nuance to keep straight is that in a clocked process the no-assignment behaviour is hold, which is a correct register and not something to default away.

Foundation13 min readVHDLDefault AssignmentLatch AvoidanceCombinationalFSMRTL

1. Engineering intuition — drive everything, then override

The root cause of a latch is "some path left this output unassigned." The cleanest fix is to make that impossible: at the top of the process, assign every output a default, so every path has already driven every output before any condition is tested. Your conditional logic then only needs to override the cases that differ from the default. You stop thinking "did I cover every branch for every output?" and instead rely on a single, always-true safety net — the default — that guarantees completeness.

2. Formal explanation — two cures, one preferred

two_cures.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- CURE A (preferred): default-then-override. Default every output FIRST, then override.
process (all)
begin
  y <= b; z <= '0';                         -- defaults drive every output on every path
  if sel = '1' then y <= a; z <= '1'; end if;
end process;
 
-- CURE B: assign every output in every branch (full coverage). Correct but verbose and fragile.
process (all)
begin
  if sel = '1' then y <= a; z <= '1';
  else            y <= b; z <= '0';          -- must remember EVERY output in EVERY branch
  end if;
end process;

Both eliminate the latch, because both guarantee every output is assigned on every path. Cure A (default-then-override) is preferred: it requires you to remember each output's default once, then only the exceptions, so it scales to many outputs and many branches without the combinatorial burden of Cure B, where forgetting one output in one branch reintroduces a latch.

3. Production RTL — defaults across a multi-output block

defaults_multioutput.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- A control decoder with several outputs — defaults make it bulletproof.
dec : process (all)
begin
  -- defaults: the "inactive / safe" value of every output
  rd_en  <= '0';
  wr_en  <= '0';
  addr   <= (others => '0');
  state  <= IDLE;
  case cmd is
    when "01" => rd_en <= '1'; addr <= raddr;          -- only override what differs
    when "10" => wr_en <= '1'; addr <= waddr;
    when "11" => state <= BUSY;
    when others => null;                                -- defaults stand
  end case;
end process;

What hardware does this become? A combinational decoder driving four outputs; each branch overrides only the outputs it changes, and the top-of-process defaults drive the rest. No output is ever unassigned, so synthesis builds pure gates with no latch — even as the block grows.

4. Hardware interpretation — comb default vs clocked hold

combinational default-needed versus clocked hold-is-correcthold = bughold = intendedcombinational processno clock guardno assign → LATCH→ must default every outputclocked processrising_edge(clk)no assign → registerHOLDS→ correct; do NOT default12
Default semantics differ by process kind. In a COMBINATIONAL process, the no-assignment behaviour is 'hold the old value' — a latch — so you must default every output to a real value at the top. In a CLOCKED process, the no-assignment behaviour is also 'hold', but that is a register correctly keeping its value between updates — which is exactly what you want, so you do NOT default registered outputs. The same 'hold' is a bug in one context and the intended function in the other.

5. Simulation interpretation — defaults always run

defaults run unconditionally then overrides replace differing outputsWake (any input)process(all)Defaults: everyoutputunconditional → all pathsdrivenOverridesif/case replace theexceptionsOutputs settlelatch-free, pure function12
Default-then-override on the timeline. On every wake the defaults assign every output first; conditional overrides then replace the few that differ. Because the defaults ran unconditionally, every output is scheduled a value on every path — no unassigned path exists, so no latch is inferred. The overrides win by last-assignment-wins (lesson 5.9). The outputs settle as a pure function of the inputs.

Default-then-override keeps outputs latch-free: they track inputs, never hold

8 cycles
Default-then-override keeps outputs latch-free: they track inputs, never holdcmd=01 → rd_en=1 (override), wr_en stays default 0cmd=01 → rd_en=1 (over…cmd=00 → both defaults stand; outputs do not HOLD prior valuescmd=00 → both defaults…cmd0001101100011000rd_en01000100wr_en00100010t0t1t2t3t4t5t6t7
With every output defaulted to its inactive value, rd_en and wr_en are pure functions of cmd — each goes active only on its command and returns to the default otherwise, never holding a stale value. That continuous input-tracking (no hold) confirms there is no latch; the defaults guarantee it regardless of how the case grows.

6. Debugging example — the new output that latched

Expected: adding a new output to a working combinational block stays latch-free. Observed: synthesis now warns of a latch on the new output. Root cause: the new output was assigned in some branches but not defaulted at the top, so the branches that do not set it leave it unassigned → a latch — even though the rest of the block was fine. Fix: add the new output to the top-of-process defaults. Engineering takeaway: every output needs a default; when you add an output to a combinational process, add its default first — that one habit prevents the most common "I only changed one thing" latch.

add_output_default.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: 'busy' added, set only in one branch, not defaulted → latch.
process (all) begin
  y <= b;                                  -- y defaulted (fine)
  if sel = '1' then y <= a; busy <= '1'; end if;   -- busy unassigned when sel='0' → latch
end process;
-- FIX: default 'busy' too.
process (all) begin
  y <= b; busy <= '0';                     -- default EVERY output
  if sel = '1' then y <= a; busy <= '1'; end if;
end process;

7. Common mistakes & what to watch for

  • Defaulting some outputs but not all. Every output of a combinational process needs a default; one un-defaulted output latches.
  • Defaulting registered outputs in a clocked process. A clocked output should hold when not assigned (that is the register); do not default it to a value every cycle unless you mean to.
  • Choosing a default that masks bugs. Pick the safe/inactive default (disabled, zero, idle) so an unexpected path fails safe and is visible.
  • Relying on full-branch coverage (Cure B) as the block grows. It is fragile; one missed output in one branch reintroduces a latch. Prefer default-then-override.
  • Putting defaults after some logic. Defaults must be at the top, before any conditional, so they truly cover every path.

8. Engineering insight & continuity

Default-then-override is the single most valuable habit in combinational RTL: one safe default per output at the top of the process, then override the exceptions. It makes latches structurally impossible and keeps large multi-output decoders and FSM output logic clean and reviewable. The complementary discipline is knowing when not to default — in a clocked process, "hold" is the register doing its job. With latch avoidance now a reflex, the module turns to the combinational building blocks themselves: the next lesson, Multiplexers, applies these patterns to the most fundamental selection element, followed by Decoders and Encoders and Arithmetic Circuits.