3.5.2 · D5HDL & Digital Design Flow
Question bank — Combinational logic in HDL
Before we start, three words we lean on everywhere below:
True or false — justify
Combinational logic can contain feedback as long as it settles
False — feedback that holds a value is memory; once a wire's value depends on its own past, you have a latch or a combinational loop, not pure
out = f(inputs).An assign statement can ever infer a latch
False — a continuous
assign is a permanent equation always driving its target, so the output is never "left unassigned"; latches only sneak in through incomplete procedural blocks.Using non-blocking <= inside a combinational always @(*) will always break the hardware
Often but not always — for a single independent output it may still synthesize correctly, yet it can cause simulation mismatches and wrong results when one line depends on another, so it is banned by convention.
A case statement with all values listed but no default is safe from latches
Only if every possible value of the selector is truly listed; for an -bit selector that means all "two-to-the-n" patterns — missing even one (or ignoring
x/z in simulation) leaves a path unassigned, so a default is the safe habit.always @(*) and always @(a or b or c) are identical when the block reads exactly a, b, c
True for that exact case —
@(*) just auto-builds the same list; they diverge only when you later add a read and forget to update the manual list.Assigning an output twice in the same block (last write wins) creates a latch
False — a default followed by a conditional override is the recommended latch-avoidance pattern; blocking
= makes the last assignment win, so the output is fully defined on every path.The procedural block always @(*) y = y & 1'b0; (output y fed back into itself) is still latch-free
False — the right-hand side reads the same output
y it is writing, so y depends on its own past value; the tool cannot resolve y from inputs alone and reports a combinational loop (and depending on the tool may infer a latch instead). Note the distinction: many synthesizers flag the loop separately and do not silently insert a latch.Spot the error
always @(*) if (sel) y = a; — what's wrong
No
else, so sel==0 leaves y unassigned; the tool inserts a transparent latch to hold the old y. Add else y = ...; or a default before the if.always @(a) y = a & b; — what's wrong
The sensitivity list omits
b; simulation won't re-run when b changes, so sim shows a stale y while synthesized gates use both inputs — a sim-vs-silicon mismatch. Use @(*).always @(*) begin y = t | c; t = a & b; end — what's wrong
Order is backwards for blocking
=: y reads t before t is computed, so y uses the previous t. Compute t first, then y.assign y = sel ? b : a; assign y = c; — what's wrong
Two continuous
assigns drive the same net; they fight, producing an x (unknown, from contention) in simulation and multiply-driven-net errors in synthesis. One net, one driver.always @(*) case(sel) 2'b00: y=a; 2'b01: y=b; endcase — what's wrong
Only 2 of 4 selector values are covered; for
sel = 2'b10 or 2'b11 y is unassigned ⇒ latch. Add default: y = ...;.always @(posedge clk) y = a & b; used as combinational logic — what's wrong
A
posedge clk edge event makes this sequential (it registers on the clock), not combinational; the output changes only on clock edges, adding a flip-flop you didn't want here.always @(*) begin y = 1'b0; if (en) y = data; z = w; end where w is a wire that no code anywhere drives — what's wrong
z is assigned from w, but w is never driven, so w floats (z in simulation) and z copies that unknown/floating value. y here is fine (default covers every path); the bug is the undriven source w — every signal you read must be driven somewhere.Why questions
Why does a partially specified output become memory rather than just a constant or error
Because "keep the old value" is a valid meaning the tool can implement — and the only hardware that holds a previous value is storage, so it faithfully builds a latch instead of guessing you made a mistake.
Why is blocking = the right choice for combinational blocks
It executes immediately and in written order, exactly mirroring how a signal ripples through chained gates within one instant, so intermediate wires carry their new values to later lines.
Why is non-blocking <= right for clocked (sequential) blocks instead
It defers all updates to the end of the time-step, so every flip-flop reads the old values and updates together on the edge — matching real registers that all sample simultaneously.
Why does @(*) prevent a whole class of bugs a manual list cannot
It re-derives the sensitivity list from every signal actually read, so you can never accidentally omit one; the simulator then re-evaluates on any relevant change, keeping sim behaviour equal to the synthesized gates.
Why do we write the Boolean function or truth table before the HDL
Combinational logic is a Boolean function; nailing the function first guarantees you cover every input case, which is precisely what stops unassigned paths and latches. See Boolean algebra and Karnaugh maps.
Why can a "default value before the branches" replace an explicit else
The default assigns the output on entry, so even if no branch fires the output already holds a fresh, defined value — every path ends assigned, so no memory is needed.
Why is an unintended latch harmful even if the logic "works" in sim
It adds an unclocked, level-sensitive memory element that creates timing hazards, races, and static-timing paths that are hard to close, plus it can glitch through while transparent. See Synthesis and the digital design flow.
Edge cases
What happens if a combinational always @(*) block reads no inputs, e.g. always @(*) y = 1'b1;
@(*) with no reads triggers once and holds; the synthesized result is simply a constant 1 driver — legal and latch-free, since the single path always assigns y.A case covers all its explicit selector values but the selector can be x (unknown) in simulation — is it safe
In synthesis it's fine (real signals are 0/1), but in RTL simulation an
x selector matches no branch and can leave y stale, giving a misleading sim latch — a default removes the ambiguity.Only one of several outputs is unassigned on some path — does the whole block become a latch
No — latch inference is per-signal; only the unassigned output gets a latch, the fully-driven ones stay combinational. So audit each output separately.
An output is assigned in every branch but the branches don't cover every input combination (nested if with no final else)
Still a latch — coverage is about input combinations, not about "there exists an assignment"; if any reachable input pattern skips all assignments, that output holds and infers a latch.
Selector is a full-range signal (e.g. all 4 patterns of a 2-bit sel explicitly listed) — is default still needed
For synthesis it's technically complete, but keeping
default is best practice: it protects against x/z in simulation and against later widening of the selector, costing nothing. Contrast with a MUX where the ternary ?: covers both cases inherently.Is a feedback that reads an output equivalent to a latch, or is it a different diagnostic
Different — a self-referencing combinational expression is usually reported as a combinational loop, which many tools flag as its own error rather than silently inferring a latch; a latch specifically comes from an incomplete assignment (a missing path), not from feedback.
Can a design be combinational yet still need <= somewhere in the same module
Yes — a module can hold separate blocks: combinational ones use
=, clocked ones use <=. The rule is per-block, not per-module. See Sequential logic in HDL and Blocking vs Non-blocking assignments.Is a wire read before it is assigned within a blocking block a latch
Not a latch but a logic bug — with blocking
=, reading a variable before its assignment uses a stale/previous value, producing wrong combinational output; reorder the statements so producers precede consumers.Recall One-line summary
A latch is the tool's honest answer to "keep the old value on this path" — so wipe and rewrite every output on every path, and combinational stays combinational.
Connections
- ← Parent: Combinational logic in HDL
- Latch inference and how to avoid it
- Blocking vs Non-blocking assignments
- Sequential logic in HDL
- Boolean algebra and Karnaugh maps
- Multiplexers, adders, decoders
- Synthesis and the digital design flow