Skip to content

Verilog · Chapter 10.11 · Operators & Operands

Conditional Operator in Verilog — The Ternary Multiplexer (?:)

The conditional operator, written as cond then question-mark a colon b, is the ternary that selects between two values based on a condition. If the condition is true the result is the first value, and if false it is the second. This single expression builds a multiplexer, the most common structure in combinational logic, and it makes the intent to select obvious to both the reader and the synthesizer, replacing a verbose hand-built gate-level mux. Chained together, ternaries form a priority multiplexer that reads like an if-else-if ladder, and they express clamps, saturation, default values, and min or max in one line. This lesson drills the operator, the mux it builds, nested ternaries and their priority, the result-width rule, unknown-condition behaviour, and how it relates to the if and case statements you will meet later.

Foundation17 min readVerilogConditional OperatorTernaryMultiplexerRTL Design

Chapter 10 · Section 10.11 · Operators & Operands

1. The Engineering Problem

An engineer needs a 2:1 multiplexer — pick in1 when sel is 1, else in0 — for an 8-bit bus, and builds it from gates:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign out = (in1 & sel) | (in0 & ~sel);     // intent: 8-bit 2:1 mux

It works for 1-bit signals and breaks for the 8-bit bus. sel is a single bit, so in1 & sel zero-extends sel to 8'b0000_000s and ANDs — selecting only bit 0 of in1, zeroing bits 7:1. The "mux" passes one bit of the selected input and drops the other seven. To fix the gate version you must replicate sel to the bus width (in1 & {8{sel}}, 10.9) — verbose and easy to get wrong.

The conditional operator is the mux, written directly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign out = sel ? in1 : in0;                 // clean 8-bit 2:1 mux ✓

`sel ? in1 : in0` reads "if sel, then in1, else in0" — and synthesizes to a multiplexer that selects the whole bus, correctly, at any width. It is shorter, obviously correct, and announces its intent (a selection) to every reader and to the synthesizer.

The conditional operator cond ? a : b selects between two values and builds a multiplexer — the cleanest, most readable way to express selection, and a fundamental building block of combinational logic. Hand-building a mux from gates is verbose and error-prone; the ternary is one expression.

This is the last operator in the chapter, and a fitting one: the multiplexer is the workhorse of datapaths, and ?: is how you write one. This page teaches the operator, the mux it builds, and the idioms — priority muxes, clamps, defaults — that make it indispensable.

2. Mental Model — The Ternary Is a Multiplexer

Visual A — the ternary builds a 2:1 mux

Conditional operator as a 2:1 multiplexeraselected when cond=1bselected when cond=02:1 muxcond = selectresultcond ? a : b12
The conditional operator cond ? a : b synthesizes to a 2:1 multiplexer: the condition drives the select line, a and b are the data inputs, and the result is the output. When cond is 1 the mux passes a; when 0 it passes b. The whole value (any width) is selected — unlike a hand-built gate mux, which needs the select replicated to the bus width.

3. The Hardware View — A Multiplexer

?: synthesizes to a multiplexer — a cheap, fundamental structure: the condition selects which data input reaches the output. A single ?: is a 2:1 mux; chained ternaries build a cascade (a priority mux, §5). The multiplexer is everywhere in combinational logic — selecting between operands, choosing a next-state value, picking a default — so ?: is among the most-used operators in a datapath. It is far cheaper than the arithmetic operators and, unlike the hand-built gate mux, correct at any width by construction.

4. The Syntax

conditional-syntax.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   cond ? value_if_true : value_if_false
 
   assign out  = sel ? in1 : in0;            // 2:1 mux
   assign y    = (a > b) ? a : b;            // max(a, b)
   assign z    = enable ? data : 8'h00;      // data when enabled, else 0
   assign safe = (v > MAX) ? MAX : v;        // clamp to MAX

The rules:

  • cond is reduced to truth — nonzero selects the true branch, zero the false (truthiness, 10.3).
  • The result width is the wider of the two branch widths, evaluated in the surrounding context. Both branches are sized to that width, so mismatched branch widths get extended (size them deliberately — DebugLab 3).
  • It is the only ternary operator in Verilog — one condition, two values.

5. Nested Ternaries — The Priority Mux

Chaining conditionals builds a priority multiplexer that reads like an if-else-if ladder. From 10.1, ?: is right-associative, so the chain nests on the false branch:

priority-mux.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 4:1 priority mux — first true condition wins:
   assign out = sel_a ? a :
                sel_b ? b :
                sel_c ? c :
                        d;        // default (none selected)
 
   // reads as: if sel_a → a; else if sel_b → b; else if sel_c → c; else d

The right-associative grouping `sel_a ? a : (sel_b ? b : (sel_c ? c : d))` is exactly the priority behaviour: conditions are tried top to bottom, the first true one wins, and the final value is the default. This is the idiomatic dataflow way to write a priority mux or a small lookup. Keep the indentation aligned (as above) so the ladder is readable — deeply nested ternaries on one line are a readability problem (§10), and for anything large a case statement (Chapter 14) is clearer.

Visual B — nested ternaries are a priority mux

Chained ?: → priority multiplexer

data flow
Chained ?: → priority multiplexersel_a ? afirst priority: sel_b ? belse if: sel_c ? celse if: ddefault (none)
A chain of conditionals is a priority multiplexer: conditions are tried top to bottom (right-associative nesting), the first true one selects its value, and the final term is the default when none match. It reads like an if-else-if ladder and builds a cascade of 2:1 muxes.

6. The Key Idioms

A few conditional patterns appear constantly in datapaths:

conditional-idioms.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 2:1 MUX — the basic selection
   out = sel ? in1 : in0;
 
   // DEFAULT / FALLBACK — a value, or a default when not valid
   result = valid ? value : DEFAULT;
 
   // CLAMP / SATURATE — limit to a maximum (or minimum)
   sat = (sum > MAX) ? MAX : sum;
   clamped = (v < MIN) ? MIN : (v > MAX) ? MAX : v;     // clamp to [MIN, MAX]
 
   // MIN / MAX
   larger  = (a > b) ? a : b;
   smaller = (a < b) ? a : b;
 
   // GATE / ENABLE — pass or force zero
   gated = enable ? data : '0;          // (sized 0)
  • Mux and default are the bread-and-butter selections — pick an input, or fall back to a default.
  • Clamp/saturate limits a value to a range — essential in arithmetic to prevent overflow wrap (a saturating adder clamps instead of wrapping). A two-sided clamp nests two ternaries.
  • Min/max is a comparison feeding a select — (a > b) ? a : b.

These combine the conditional with the relational operators (10.6): the comparison produces the condition, the ternary selects on it.

7. The Unknown-Condition Behaviour

When the condition is x (unknown), the conditional operator does not pick a branch arbitrarily — it produces a bitwise blend: for each output bit, if the two branches agree on that bit, that value passes; where they differ, the result bit is x:

conditional-x.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // cond is x; branches agree on some bits, differ on others
   1'bx ? 4'b1100 : 4'b1010    // = 4'b1xx0
   //       agree: bit3(1), bit0(0); differ: bit2, bit1 → x
 
   1'bx ? 4'b1111 : 4'b1111    // = 4'b1111  — branches identical, no uncertainty

This is sensible X-pessimism: with an unknown select, the result is known only where both inputs would give the same answer, and unknown where the choice matters. It is also a useful debugging signal — an x appearing in a mux output points to an unknown select. (In synthesizable hardware the select is always a real 0 or 1; this behaviour is a simulation-time property.)

8. ?: vs if/case — Dataflow vs Procedural Selection

The conditional operator is the expression-level selector; the if and case statements (Chapter 14) are the statement-level selectors. They build the same multiplexer hardware but live in different places:

ternary-vs-if.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // DATAFLOW — a single selected value in a continuous assignment:
   assign out = sel ? in1 : in0;
 
   // PROCEDURAL — the same selection as an if statement in an always block:
   always @(*)
       if (sel) out = in1;
       else     out = in0;
  • Use ?: when you need one selected value in an expression — a mux feeding an assign (Chapter 13), or a sub-expression inside a larger computation. It is compact and reads as "this value or that."
  • Use if/case (Chapter 14) when the selection drives statements or multiple assignments, or when there are many cases — a case is clearer than a deep ternary chain for a large mux.

Both are synthesizable muxes; the choice is about expression vs statement and readability. ?: is the right tool when a single value is being selected inline — which is exactly the dataflow style of the next chapter.

9. Worked Examples

9.1 Example 1 — a clean 2:1 mux

mux2to1.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module mux2 #(parameter integer WIDTH = 8)(
    input  [WIDTH-1:0] in0, in1,
    input              sel,
    output [WIDTH-1:0] out
);
    assign out = sel ? in1 : in0;       // selects the whole WIDTH-bit bus
endmodule

The §1 fix: `sel ? in1 : in0` is a width-independent 2:1 mux that selects the entire bus correctly, no select-replication needed. Compare the gate version ((in1 & {WIDTH{sel}}) | (in0 & {WIDTH{~sel}})) — correct but verbose and easy to get wrong. The ternary is the idiomatic mux.

9.2 Example 2 — a saturating adder

saturate.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module sat_add (
    input  [7:0] a, b,
    output [7:0] sum
);
    wire [8:0] full = a + b;            // 9-bit sum captures the carry (10.2/10.10)
    assign sum = full[8] ? 8'hFF : full[7:0];   // overflow? clamp to max : pass
endmodule

A saturating adder uses the carry bit full[8] as the condition: if the add overflowed (carry set), clamp the output to 8'hFF; otherwise pass the low 8 bits. The ternary turns the overflow flag into a clamp — preventing the modular wrap of a plain 8-bit add. This combines carry capture (10.10), comparison-by-carry, and conditional selection.

9.3 Example 3 — a 4:1 priority mux

priority4.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module priority_mux (
    input  [7:0] a, b, c, d,
    input        sa, sb, sc,
    output [7:0] out
);
    assign out = sa ? a :
                 sb ? b :
                 sc ? c :
                      d;                // first asserted select wins; d is default
endmodule

A chained ternary builds a 4:1 priority mux: sa has highest priority, then sb, then sc, with d as the default when none assert. The aligned indentation makes the priority ladder readable. For a larger or non-priority selection, a case (Chapter 14) is clearer — but for a small priority mux, the ternary chain is idiomatic.

10. Common Mistakes

  1. Hand-building a mux from gates instead of ?: — verbose and breaks at multi-bit widths without replicating the select (§1, DebugLab 1).
  2. Mismatched branch widths — the narrower branch extends to the wider; size both deliberately (§4, DebugLab 3).
  3. Deeply nested one-line ternaries — unreadable; align the ladder or use a case (§5).
  4. Misreading nested-ternary priority?: is right-associative; the chain is an if-else-if priority ladder (DebugLab 2).
  5. Forgetting the condition is truthiness — any nonzero condition selects the true branch.

11. Debugging Lab

Three conditional-operator debug post-mortems

12. Interview Q&A

13. Exercises

Exercise 1 — Evaluate the ternaries

Give each result: (a) 1'b1 ? 8'hAA : 8'h55; (b) 1'b0 ? 8'hAA : 8'h55; (c) (3 > 5) ? 4'h1 : 4'h2; (d) 4'b0010 ? 8'hFF : 8'h00 (note the condition is multi-bit); (e) 1'bx ? 4'b1010 : 4'b1000.

Exercise 2 — Write the selection

Write a ternary expression for each: (a) a 2:1 mux selecting x or y on sel; (b) max(a, b); (c) clamp v to a maximum of MAX; (d) a 3:1 priority mux: p0 ? a : p1 ? b : c.

Exercise 3 — Fix the selectors

Each line states intent. Identify the bug and fix it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
out = (in1 & sel) | (in0 & ~sel);    // intent: 8-bit 2:1 mux
out = lo ? b : hi ? a : c;           // intent: hi has higher priority than lo
out = use_w ? w8 : s4;               // intent: select 8-bit w8 or zero-extended 4-bit s4

Exercise 4 — Reason about ternaries

(a) Why does sel ? in1 : in0 work for a bus while (in1 & sel) | (in0 & ~sel) does not (for a 1-bit sel)? (b) In a ? x : b ? y : z, which condition has priority, and why? (c) Why is a deeply nested one-line ternary discouraged, and what is the alternative for a large selection?

14. Summary

The conditional operator cond ? a : b selects between two values and builds a multiplexer — the cleanest, most readable way to express selection in combinational logic, and the closing operator of Chapter 10.

The core ideas:

  • cond ? a : b is a 2:1 mux — the condition (reduced to truth) is the select line; true picks a, false picks b; the whole value is selected at any width.
  • Chained, it is a priority muxs1 ? a : s2 ? b : c reads as if-else-if (right-associative); top-to-bottom order is the priority order.
  • It expresses datapath idioms in one line — muxes, defaults, clamps/saturation, min/max — pairing with the relational operators for the condition.
  • An x condition gives a bitwise blend — known where both branches agree, x where they differ.
  • ?: is the dataflow mux; if/case (Chapter 14) are the procedural muxes — same hardware, expression vs statement.

The discipline this page instils:

  • Use ?: for muxes, not hand-built gates — correct at any width, readable, intent-revealing.
  • Order a ternary chain by priority — first condition wins.
  • Size both branches deliberately — the result takes the wider; extend explicitly when it matters.

Chapter 10 complete

With the conditional operator, Chapter 10 — Operators & Operands — is complete. You now command the full operator vocabulary of combinational logic: precedence (10.1), arithmetic (10.2), logical (10.3), bitwise (10.4), reduction (10.5), relational (10.6), shift (10.7), equality (10.8), replication (10.9), concatenation (10.10), and the conditional (10.11). Every one of them infers hardware — adders, comparators, gates, shifters, muxes — and every combinational function you will ever write is some composition of them.

What remains is to place these expressions into a design. The RTL core continues with the constructs that host expressions:

Chapter 13 Dataflow (assign) → Chapter 14 Behavioural (always, blocking vs non-blocking, combinational and sequential logic).

Dataflow modeling (assign) is where these operator expressions become continuous-assignment hardware — the natural next step, since the conditional operator you just learned is the dataflow mux. From there, procedural always blocks add the sequential, clocked logic that turns combinational expressions into real, stateful RTL.