Combinational logic in HDL
WHAT is combinational logic?
Contrast with sequential logic, which remembers past inputs (flip-flops, latches, clocks). The whole art of writing combinational HDL is: describe a function completely, so the tool never has to "remember" anything.
WHY does HDL style matter? (The latch trap)
Synthesis tools read your behavioral description and infer hardware. If your code ever says "in some case, keep the old value," the tool must insert memory — a latch. That is almost never what you want in a combinational block.
HOW to write it — three idioms
1. Continuous assignment (assign) — Verilog
assign y = (a & b) | ~c;- WHY it's combinational: an
assignis a permanent equation that re-evaluates whenever any right-hand-side signal changes. There is no way to leaveyundriven.
2. Procedural always @(*) block — Verilog
always @(*) begin // sensitivity = ALL read signals
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
default: y = d; // ← the default that saves you
endcase
end- WHY
@(*): the*auto-builds the sensitivity list from every signal read. Miss a signal in a manual list and simulation ≠ hardware. - WHY
default: it guaranteesyis assigned on every path ⇒ no latch.
3. VHDL equivalent
process(all) -- VHDL-2008 "all" = the (*)
begin
case sel is
when "00" => y <= a;
when "01" => y <= b;
when others => y <= c; -- covers all remaining
end case;
end process;
Blocking vs non-blocking (the #1 confusion)
WHY: blocking = executes immediately and in order, mimicking how signals ripple through gates in the same instant. Non-blocking <= schedules updates for the end of the timestep, mimicking flip-flops that all update together on a clock edge.
Worked example 1 — 2-to-1 MUX (derive from truth table)
Truth table:
| sel | y |
|---|---|
| 0 | a |
| 1 | b |
Step 1 — Boolean function. . Why this step? Combinational logic is a Boolean function; write the function before the code.
Step 2 — Code it three ways.
assign y = sel ? b : a; // idiom 1always @(*) y = sel ? b : a; // idiom 2, single line auto-completeWhy this step? Both fully specify y for sel=0 and sel=1 ⇒ combinational, no latch.
Worked example 2 — steel-manning a latch bug
always @(*) begin
if (enable)
y = data; // ← what if enable == 0 ??
endWhy the wrong code feels right: "I only care about y when enable is high." Feels efficient — why write the else?
What actually happens: when enable==0, y is not assigned, so the tool must hold the old value ⇒ it infers a transparent latch. Now y has memory you never intended — timing hazards, hard-to-close design.
Worked example 3 — full adder from first principles
Step 1 — Truth table → equations. Sum is 1 when an odd number of inputs are 1; carry when ≥2 are 1: Why this step? Derive from counting 1s — no memorization.
Step 2 — HDL:
assign s = a ^ b ^ cin;
assign cout = (a & b) | (cin & (a ^ b));Why this step? Two continuous assigns ⇒ every output permanently driven ⇒ guaranteed combinational.
Recall Feynman: explain to a 12-year-old
Imagine a vending machine that has no memory. You press buttons (inputs) and instantly a snack drops out (output) based only on what you pressed right now — it never remembers your last visit. Combinational logic is that machine. The catch: if you forget to tell the machine what to do in some situation, it gets confused and starts "remembering" the last snack forever — that accidental memory is called a latch, and it's a bug. So you must give it a rule for every button combination.
Active recall
Combinational logic depends on what?
What hardware does an unassigned output in a combinational block infer?
Why use always @(*) instead of a manual sensitivity list?
* auto-includes every read signal, preventing simulation-vs-synthesis mismatch.Blocking (=) or non-blocking (<=) in a combinational always block?
=, because it executes in order like signals rippling through gates.Two ways to prevent latch inference in an always @(*)?
default/else covering all paths.Write the sum equation of a full adder.
Write the carry-out of a full adder.
Why is a continuous assign always combinational?
Connections
- Sequential logic in HDL — the counterpart that does keep state (flip-flops, clocks).
- Latch inference and how to avoid it
- Blocking vs Non-blocking assignments
- Boolean algebra and Karnaugh maps — where the functions come from.
- Synthesis and the digital design flow
- Multiplexers, adders, decoders — canonical combinational building blocks.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Combinational logic ka matlab hai aisa hardware jiska output sirf abhi ke inputs pe depend karta hai — koi memory nahi, koi clock nahi. Jaise ek calculator ka + — jo abhi daala wahi jud gaya, purana kuch yaad nahi rakhta. HDL me hum bas relationship describe karte hain (equation ya case), aur synthesizer usse gates bana deta hai.
Sabse bada trap hai latch inference. Agar tumne always @(*) block me kisi case me output ko assign hi nahi kiya (jaise if (en) y = data; likha par else chhod diya), to tool sochta hai "en==0 pe y ki purani value hold karni hai" — aur wo latch ghusa deta hai, yaani anchaahi memory. Ye bug timing problems deta hai. Fix simple hai: har output ko har path pe assign karo — ya default/else do, ya block ke shuru me ek default value set kar do.
Do aur cheezein yaad rakho. Pehla: sensitivity list me hamesha @(*) use karo, warna simulation kuch aur dikhayega aur real chip kuch aur karegi (sim ≠ silicon). Doosra: combinational block me blocking = use karo (kyunki signals gates me turant ripple karte hain), aur clocked/sequential block me non-blocking <=. Bas itna samajh lo — CAB-L mnemonic — to combinational HDL me tum kabhi phasoge nahi.