Operators and Expressions
The Conditional (Ternary) Operator in Verilog
How condition ? if_true : if_false describes a combinational multiplexer in one expression, and why nested ternaries need explicit parentheses.
4 min read · Updated
The conditional operator condition ? if_true : if_false is a single expression that selects between two values. y = sel ? a : b synthesizes to a 2-to-1 multiplexer: hardware that continuously routes a or b to y based on sel, with no storage. Hardware it becomes: a plain 2-input mux — the select line steers one of two already-computed values to the output; both a and b exist in the hardware the whole time, only the routing changes. See MUX Design: Choosing a Style for when a ternary reads better than an if/else and when it does not.
For a hardware sel that is genuinely 0 or 1, a ternary and an equivalent if/else inside always @(*) describe the same mux. They are not interchangeable as a general simulation-semantics statement, though: when the condition itself is x or z, the conditional operator can resolve to a known result by comparing the two branches bit-by-bit — if both branches agree, the ambiguity doesn't matter and the known value wins; only a genuine disagreement produces x. An if with an ambiguous condition has no such blending: the language treats anything other than a definite true as false, so it unconditionally takes the else branch. Four-State Logic covers x/z in more depth; the demonstration below shows the divergence directly rather than asserting it.
Ternaries can nest: req0 ? val0 : (req1 ? val1 : val2) describes a priority chain, checking req0 first and falling through toward the last, default value. The conditional operator associates right-to-left, so a ? x : b ? y : z technically parses the way that nested form suggests — but relying on the reader to know that rule is worse than writing the parentheses explicitly.
Predict before running
Before running it, predict y for sel = 0 and sel = 1, which value a two-level priority chain picks when both req0 and req1 are asserted — and whether a ternary and an equivalent if/else still agree once the select itself becomes x.
Expected output — reveal after you predict
sel=0 a=5 b=a y=a
sel=1 a=5 b=a y=5
req0=0 req1=1 priority_pick=1
req0=1 req1=1 priority_pick=0
amb_sel=x amb_a=1 amb_b=0 ternary=x if_else=0 (branches disagree)
amb_sel=x amb_a=1 amb_b=1 ternary=1 if_else=1 (branches agree)
PASSsel=0 selects b, sel=1 selects a — the mapping is only meaningful in light of the code, which is exactly why an unlabelled sel ? a : b mux benefits from a comment or a well-named select signal. The priority chain picks req1's value (1) when only req1 is asserted, but req0's value (0) once req0 is also asserted — req0 is checked first regardless of what else is true, which is the entire point of a priority encoding.
The last two lines are the divergence promised above. With amb_sel unknown and the branches disagreeing (1 vs 0), the ternary reports x — it genuinely cannot determine the answer — while the if/else reports 0, because an ambiguous condition is simply treated as not-true and takes the else branch regardless of what the "true" branch would have produced. Once the branches agree (both 1), the ternary resolves to the known value 1 even though amb_sel is still unknown, while the if/else — still taking else for the same reason as before — happens to report the same 1 only because this particular else branch also holds 1. The two constructs are not computing the same thing; they coincide here by construction of the second test, not by definition.
Common mistakes
- Nesting ternaries without parentheses "because precedence handles it." It does, but a reader — including future you — should not have to recall an associativity rule to know which condition is checked first. Parenthesize nested conditionals explicitly.
- Treating
sel ? a : band an equivalentif/elseas always interchangeable. They agree for a definite 0/1 select. They can diverge when the select isx/z, as shown above — a real concern in testbenches, reset transitions, and anywhere a signal hasn't settled yet, not just a textbook curiosity. - Using a ternary chain where a
casestatement would be clearer. A ternary chain reads fine for two or three options; beyond that,caseStatements usually communicates the selection more clearly than a long nested chain.
Sources and verification
Example provenance: SkillLift Labs authored the example for this tutorial. tests/scripts/tutorial-verified-examples.test.ts extracts the complete marked example from this Markdown file, compiles it in Verilog-2005 mode with Icarus Verilog, runs it, and requires the simulator output to match the Expected output block exactly. The same test also carries an independent, code-owned semantic oracle, and exercises the article-to-runner source-splitting path. Automation verifies those stated properties; human technical review for indexing remains a separate gate.