Tricky Problem: Writing Synthesizable RTL for a 2:1 Multiplexer
Write minimal, synthesizable, purely combinational Verilog for a 2:1 MUX, and recognize how an incomplete always block covering the same function can accidentally infer an unwanted latch.
Write synthesizable RTL for a 2:1 multiplexer. Expected structure: assign y = sel ? d1 : d0;
Why This Problem Is Tricky
The one-line continuous assignment assign y = sel ? d1 : d0; is trivially correct and maps directly to a clean combinational MUX. The trap surfaces when a learner 'over-engineers' the same function using an always @(*) block instead, and forgets to assign the output for every possible branch. If one branch of an if/else (or a case statement) fails to assign the output, a synthesis tool cannot determine what the output should hold in that missing condition, so it inserts a LATCH to 'remember' the previous value — silently turning an intended stateless combinational circuit into a sequential (memory) element with completely different timing and behavior.
Step-by-Step Worked Solution
Function needed: y = d1 when sel = 1, and y = d0 when sel = 0 — a pure combinational function of sel, d0, and d1 with no memory.
Cleanest synthesizable form: a continuous assignment using the ternary conditional operator: assign y = sel ? d1 : d0; This single statement directly and unambiguously describes the desired combinational MUX. There is no clock and no notion of a 'previous value' anywhere in this statement — it always, continuously, describes y purely in terms of the current values of sel, d0, and d1.
An equivalent, but riskier, alternative is an always @(*) block: always @(*) begin if (sel) y = d1; else y = d0; end. This is correct ONLY because BOTH branches (the if and the else) explicitly assign y. If the else branch were removed, y would be left unassigned whenever sel=0, and synthesis tools resolve that ambiguity by inferring a latch that holds y at its last value instead of driving it combinationally.
Conclusion: assign y = sel ? d1 : d0; is the preferred, minimal, and safest form specifically because the ternary operator's syntax structurally requires BOTH a true-result and a false-result — it is impossible to accidentally omit one branch the way an if/else or case statement allows.
Why the Obvious Wrong Answer Is Wrong
The trap answer is an always @(*) block with only the if-branch present and no else: always @(*) if (sel) y = d1; — with the reasoning 'the unselected case just isn't reached, so this still describes a MUX.' In simulation, this might even appear to work by coincidence if y happens to already hold the right value from a previous assignment or initial condition. But synthesis tools interpret any combinational always block where an output is not assigned on every possible path as requiring memory for that output — so this code does NOT synthesize to a simple 2:1 MUX; it synthesizes to a MUX combined with an unintended latch, which changes the circuit's timing, power, and testability characteristics entirely, and is flagged as a design-rule violation by most linting tools.
2:1 MUX — Recommended Synthesizable Form
The ternary operator's syntax always supplies both a true-result and a false-result, so it structurally cannot omit a case the way an if/else or case statement can. This is a continuous assignment (outside any always block), which is the correct construct for describing purely combinational logic with no memory.
assign y = sel ? d1 : d0;Code Examples
The first form is the recommended minimal solution. The second form is functionally equivalent and still purely combinational, but only because it explicitly covers both branches. The third form looks superficially similar to the second but omits the else branch, which causes synthesis tools to infer a latch to hold y's previous value whenever sel=0 — a completely different (and unintended) circuit. Expected result: y = d1 when sel=1, y = d0 when sel=0; purely combinational, no latch inferred.
assign y = sel ? d1 : d0;
// GOOD -- pure combinational, no risk of latch inference
assign y = sel ? d1 : d0;
// ALSO GOOD -- always block with BOTH branches assigning y
always @(*) begin
if (sel) y = d1;
else y = d0;
end
// BAD -- missing else branch, infers an unintended latch
always @(*) begin
if (sel) y = d1;
// no else -- y is undefined when sel=0, so synthesis adds a latch
end2:1 MUX Block and RTL Comparison
d0 --->+
| 2:1
d1 --->| MUX ---> y
|
sel --->+
GOOD (combinational): BAD (infers a latch):
assign y = sel ? d1 : d0; always @(*)
if (sel) y = d1;
// missing else -> latch!Why Latch Inference Is a Real Design Hazard
Unintended latch inference from incomplete combinational always blocks is one of the most common and most dangerous synthesis pitfalls in real RTL design. Latches are asynchronous, notoriously difficult to time-close during static timing analysis, and are frequently flagged as outright design-rule violations in professional ASIC and FPGA design flows — RTL linting tools exist specifically to catch exactly this class of bug (an always @(*) block with a path that does not assign every output) before the design ever reaches synthesis or silicon.
always @(*) if (sel) y = d1; (no else branch) assign y = sel ? d1: d0; (or an always block with BOTH if and else present)
Omitting a branch or condition in a combinational always block leaves the output unassigned for that case, and synthesis tools resolve that ambiguity by inferring a latch to hold the previous value — turning an intended stateless MUX into an unintended sequential element.
What is the standard synthesizable Verilog structure for a 2:1 multiplexer? A. always @(*) if (sel) y = d1; B. assign y = sel ? d1 : d0; C. initial y = sel ? d1 : d0; D. assign y = sel & d1 | d0;
B. assign y = sel ? d1 : d0; — assign y = sel ? d1 : d0; is a continuous assignment using the ternary operator, which always supplies both outcomes (d1 for sel=1, d0 for sel=0) and maps directly to a clean combinational MUX. The always block without an else (option 1) risks latch inference; initial (option 3) only runs once in simulation and is not synthesizable combinational logic; the plain AND/OR expression (option 4) is not functionally equivalent unless sel is properly combined with both data inputs' complements.
In an always @(*) block describing intended combinational logic, what specifically causes a synthesis tool to infer an unwanted latch? A. Using a case statement instead of if/else B. Leaving some possible condition (e.g., a missing else or default) without an assignment to the output C. Using blocking assignments (=) instead of non-blocking (<=) D. Declaring the output as a wire instead of a reg/logic type
B. Leaving some possible condition (e.g., a missing else or default) without an assignment to the output — A synthesis tool infers a latch whenever a combinational always block has a code path where the output signal is not assigned any value — since the output must still resolve to something, the tool assumes it should retain its previous value in that case, which requires a memory element (a latch). Using case vs. if/else, or the choice of blocking assignment, does not by itself cause latch inference as long as every path assigns the output; the missing assignment on some path is the actual root cause.
Write synthesizable Verilog for a 4:1 multiplexer with a 2-bit select input sel[1:0] and data inputs d0, d1, d2, d3, using a single construct that carries no risk of latch inference.
assign y = (sel == 2'b00) ? d0 : (sel == 2'b01) ? d1 : (sel == 2'b10) ? d2 : d3; This chain of ternary operators covers all four possible values of sel (the final 'd3' acts as the default for sel=2'b11, and implicitly for any other value), so y is always assigned and no latch is inferred.
Consider chained ternary operators, where the final else-branch acts as the default for every remaining select value.