Skip to article

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.

module mux2 (
  input  wire        sel,
  input  wire [3:0]  a,
  input  wire [3:0]  b,
  output wire [3:0]  y
);
  assign y = sel ? a : b;
endmodule

module tb;
  reg sel;
  reg [3:0] a, b;
  wire [3:0] y;
  reg [3:0] priority_pick;
  reg req0, req1;
  reg amb_sel, amb_a, amb_b;
  reg ternary_result, if_result;

  mux2 u_mux (.sel(sel), .a(a), .b(b), .y(y));

  initial begin
    a = 4'h5;
    b = 4'hA;

    sel = 1'b0;
    #1;
    $display("sel=%b a=%h b=%h y=%h", sel, a, b, y);

    sel = 1'b1;
    #1;
    $display("sel=%b a=%h b=%h y=%h", sel, a, b, y);

    req0 = 1'b0;
    req1 = 1'b1;
    priority_pick = req0 ? 4'd0 : (req1 ? 4'd1 : 4'd2);
    $display("req0=%b req1=%b priority_pick=%0d", req0, req1, priority_pick);

    req0 = 1'b1;
    req1 = 1'b1;
    priority_pick = req0 ? 4'd0 : (req1 ? 4'd1 : 4'd2);
    $display("req0=%b req1=%b priority_pick=%0d", req0, req1, priority_pick);

    // An ambiguous select: the ternary and an equivalent if/else are NOT
    // interchangeable here.
    amb_sel = 1'bx;
    amb_a = 1'b1;
    amb_b = 1'b0;
    ternary_result = amb_sel ? amb_a : amb_b;
    if (amb_sel) if_result = amb_a; else if_result = amb_b;
    $display("amb_sel=%b amb_a=%b amb_b=%b  ternary=%b if_else=%b (branches disagree)",
      amb_sel, amb_a, amb_b, ternary_result, if_result);

    amb_a = 1'b1;
    amb_b = 1'b1;
    ternary_result = amb_sel ? amb_a : amb_b;
    if (amb_sel) if_result = amb_a; else if_result = amb_b;
    $display("amb_sel=%b amb_a=%b amb_b=%b  ternary=%b if_else=%b (branches agree)",
      amb_sel, amb_a, amb_b, ternary_result, if_result);

    $display("PASS");
    $finish;
  end
endmodule
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)
PASS

sel=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 : b and an equivalent if/else as always interchangeable. They agree for a definite 0/1 select. They can diverge when the select is x/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 case statement would be clearer. A ternary chain reads fine for two or three options; beyond that, case Statements 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.