Why Learn Conditional Statements? Every decision a chip makes, such as which instruction to run, which bus master gets access or whether a packet is dropped, is described in RTL with a conditional statement. The construct you pick also shapes the hardware: the same choice written three ways can become a mux, a priority chain or an unintended latch. In this tutorial, you'll learn the three conditional constructs in Verilog and what each one builds.

Verilog conditional statements select which value is assigned, or which statements run, based on a condition. Verilog has three of them: the conditional operator ? :, the if-else statement and the case statement. In synthesizable RTL, each one describes selection hardware, usually a multiplexer.

12 min read | Beginner Level

What You'll Learn

  • The syntax of the conditional operator, if-else and case, and where each can be used
  • What hardware each construct synthesizes into
  • How to choose the right construct for a given piece of logic
  • How x values, multi-bit conditions and missing branches affect the result

Quick Reference

All three constructs choose between alternatives. They differ in where you can write them and how many alternatives they handle well.

ConstructWhere it can be usedChoicesTypical hardware
? :assign statements and procedural blocks2 per operator2 to 1 mux
if-elseProcedural blocks only (always, initial)2, or more with else if2 to 1 mux, or a priority chain
caseProcedural blocks onlyManyParallel N to 1 mux

// Conditional operator: inside a continuous assignment
assign y = sel ? a : b;

// if-else: inside a procedural block
always @(*)
  if (sel) y = a;
  else     y = b;

// case: inside a procedural block
always @(*)
  case (sel)
    1'b1    : y = a;
    default : y = b;
  endcase

These three blocks all describe the same 2 to 1 mux. In the last two, y must be declared as reg because it is assigned in an always block.

Conditional Operator

The conditional operator ? :, also called the ternary operator, picks one of two expressions based on a condition. It is an expression, not a statement, so it can appear anywhere a value is expected, including the right-hand side of an assign statement.


<variable> = <condition> ? <expression_1> : <expression_2>;
  • <condition> - Evaluated first. Any non-zero value counts as true (required)
  • <expression_1> - Result when the condition is true (required)
  • <expression_2> - Result when the condition is false (required)

module max2 (
  input  [7:0] a,
  input  [7:0] b,
  output [7:0] max     // Driven by assign, so it stays a wire
);

  // If a is greater than b, output a; otherwise output b
  assign max = (a > b) ? a : b;

endmodule

Synthesis result: Each ? : becomes a 2 to 1 mux. Its select line comes from the condition logic, here an 8-bit magnitude comparator for a > b.

Nested conditional operators

The second or third operand can itself be a conditional expression, which lets one assignment choose between more than two values.


// "y" is assigned to "out" when both "a < b" and "x % 2" are true
// "z" is assigned to "out" when "a < b" is true and "x % 2" is false
// 0 is assigned to "out" when "a < b" is false
assign out = (a < b) ?  (x % 2) ? y : z : 0;

The operator groups from right to left, so the line above is read as (a < b) ? ((x % 2) ? y : z) : 0. Adding those inner parentheses yourself makes the intent clear to the next reader. Here, x % 2 is the remainder after dividing by 2, which is just the least significant bit of x, so synthesis needs no divider.

Synthesis result: Each nesting level adds one 2 to 1 mux to the path, forming a mux tree. The 4 to 1 multiplexer is the standard example: three conditional operators in a two-level tree.

Advantages:

  • Concise: a full selection fits in a single line
  • Works in continuous assignments, where if-else and case are not allowed
  • Easy to read for simple two-way choices

Disadvantages:

  • Each operator chooses between only two values
  • Readability drops quickly beyond two levels of nesting
  • Each expression can assign only one signal; selecting several signals needs one operator per signal

If a conditional expression needs more than two levels of nesting, rewrite it as a case statement inside an always @(*) block. The hardware is the same, and the code is much easier to review.

if-else statement

The if-else statement runs one group of statements when its condition is true and another when it is false. Unlike the conditional operator, it is a procedural statement, so it can only appear inside an always or initial block, and each branch can assign several signals. For chains of conditions, see Read more on Verilog if-else-if statements.


if (<condition>) begin
	// statement 1
end else begin
	// statement 2
end
  • <condition> - Any expression; non-zero is true, zero is false (required)
  • begin ... end - Groups several statements into one branch; optional when a branch has a single statement
  • else - Runs when the condition is false (optional, but see the latch warning below)

module abs_diff (
  input      [7:0] a,
  input      [7:0] b,
  output reg [7:0] diff,     // |a - b|
  output reg       a_bigger  // 1 when a > b
);

  // Both outputs are assigned in every branch, so this is pure combinational logic
  always @(*) begin
    if (a > b) begin
      diff     = a - b;
      a_bigger = 1'b1;
    end else begin
      diff     = b - a;
      a_bigger = 1'b0;
    end
  end

endmodule

Synthesis result: A single if-else becomes one 2 to 1 mux per assigned signal, all controlled by the condition. Here the tool builds a comparator for a > b, two subtractors, and a mux that selects which difference reaches diff. a_bigger reduces to the comparator output itself. A chain of else if branches describes a priority chain, which keeps its ordering only when the conditions can be true at the same time; see Verilog Priority Encoder.

An if without an else can create a latch.

In a combinational always @(*) block, if any path leaves a signal unassigned, that signal must keep its old value, and synthesis infers a latch to store it. Either write the else branch or assign a default value at the top of the block. In a clocked always @(posedge clk) block, a missing else is fine: the flip-flop simply holds its value. See D Latch for more on latch inference.

Where are if-else Statements Used?

Register update logic in almost every block follows the same shape: if (reset) ... else if (load) ... else if (enable) .... Pipeline registers, FIFO pointers and configuration registers in peripherals all use it. The order of the branches sets which control signal wins when several are active in the same cycle, and that priority is part of the design's specification.

Quick Check

Q: Can you write if (sel) y = a; else y = b; directly inside a module, outside any always block?

Show Answer

No. if-else is a procedural statement and must be inside an always or initial block. Outside one, use the conditional operator: assign y = sel ? a : b;

case statement

The case statement compares one expression against a list of values and runs the branch whose value matches. It is the clearest way to write a choice between many alternatives, such as an opcode decoder or a state machine. For more detail, including casez and casex, see Read more on Verilog case statement.


case (<expression>)
   value1: statement1;
   value2: statement2;
   ...
   default: statementN;
endcase
  • <expression> - The value being tested, often a select or opcode signal (required)
  • value1, value2, ... - Case items, compared bit for bit with the expression (at least one required)
  • default - Runs when no item matches (optional, but recommended in combinational logic)

module seg_decoder (
  input      [1:0] digit,    // Value to display: 0 to 3
  output reg [6:0] seg       // Segments g..a of a 7-segment display, 1 = on
);

  always @(*) begin
    case (digit)
      2'd0    : seg = 7'b011_1111;   // Segments a-f on
      2'd1    : seg = 7'b000_0110;   // Segments b, c on
      2'd2    : seg = 7'b101_1011;   // Segments a, b, d, e, g on
      2'd3    : seg = 7'b100_1111;   // Segments a, b, c, d, g on
      default : seg = 7'b000_0000;   // All off; also covers x or z on digit
    endcase
  end

endmodule

Synthesis result: When the case items are distinct constants, as here, synthesis builds a parallel mux (or, for constant outputs like these, equivalent decode logic). Every input value has the same path length to the output, with no priority between items. Only the first matching item runs, and unlike C, there is no fall-through to the next item, so no break is needed.

Instruction Decoders

A processor's decode stage reads the opcode field of each instruction and generates the control signals for the rest of the pipeline, such as which ALU operation to perform, whether to read memory and which register to write. This is written as a large case statement on the opcode, with one item per instruction type and a default that flags an illegal instruction.

State Machines

Finite state machines in protocol controllers, such as UART, SPI and memory interfaces, use a case statement on the current state to compute the next state and the outputs. Each item handles one state, which keeps the code aligned with the state diagram. See Verilog FSM.

Which One Should You Use?

SituationBest choiceReason
Simple two-way choice in a continuous assignment? :The only option outside procedural blocks
Two-way choice that sets several signalsif-elseOne condition controls all assignments in the branch
Inputs that must be checked in priority orderif-else ifThe order of the branches is the priority
One signal compared against many constant valuescaseReads like a truth table and builds a parallel mux
Wildcard matching, such as 4'b1???casezTreats ? bits as don't care

Quick Check

Q: A 3-bit op selects one of eight ALU operations. Which construct gives the most readable code, and what does it synthesize into?

Show Answer

A case statement on op, with one item per operation and a default. Because the items are distinct constants, it synthesizes into a parallel 8 to 1 mux that selects among the results of the eight operations.

Common Beginner Mistakes

Mistake #1: Using = instead of == in a condition

Wrong: if (sel = 1'b1) y = a;
Right: if (sel == 1'b1) y = a;

Why? In Verilog, = is an assignment, not a comparison, and it is not allowed inside a condition. The compiler reports a syntax error. Use == to test equality.

Mistake #2: Using a multi-bit signal as a true/false condition

Wrong: assign y = sel ? b : a; with a 2-bit sel, expecting four choices
Right: Compare explicitly, such as (sel == 2'b01), or use a case statement on sel

Why? A condition is true for any non-zero value, so sel = 1, 2 and 3 all select b. The code compiles without a warning, and synthesis builds a 2 to 1 mux controlled by |sel.

Mistake #3: Forgetting that an x condition does not behave the same in if and ? :

Wrong: Assuming if (sel) and sel ? a : b always simulate identically
Right: Reset or initialize control signals so they are never x when used

Why? When a condition is x or z, if treats it as false and runs the else branch, which can hide an uninitialized signal by producing a clean value. The conditional operator instead compares the two results bit by bit: bits that are equal pass through, and bits that differ become x. The same RTL can therefore show different values in simulation depending on the construct, while the synthesized hardware is the same.

Mistake #4: Missing else or default in combinational logic

Wrong: always @(*) case (op) 2'b00: y = a; 2'b01: y = b; endcase
Right: Add default: y = 0;, or assign y = 0; before the case

Why? When op is 2'b10 or 2'b11, y is not assigned, so synthesis infers a latch to hold its old value. Every conditional in a combinational block must assign every output on every path.

Practice Exercise

Challenge: A Small ALU with All Three Constructs

Write a module alu4 with two 4-bit inputs a and b, a 2-bit op, a 4-bit output result and a 1-bit output zero. Use:

  • a case statement on op for the operation: 00 = add, 01 = subtract, 10 = AND, 11 = OR
  • a conditional operator for zero, which is 1 when result is 0
Hint

result is assigned in an always @(*) block, so declare it as reg. zero can be a wire driven by an assign statement. Give the case a default so no latch is inferred.

Solution

module alu4 (
  input      [3:0] a,
  input      [3:0] b,
  input      [1:0] op,       // Operation select
  output reg [3:0] result,
  output           zero      // 1 when result is 0
);

  // case: one item per operation, so this builds a parallel 4 to 1 mux
  always @(*) begin
    case (op)
      2'b00   : result = a + b;   // Add (carry out is dropped)
      2'b01   : result = a - b;   // Subtract
      2'b10   : result = a & b;   // Bitwise AND
      2'b11   : result = a | b;   // Bitwise OR
      default : result = 4'b0;    // Covers x or z on op; prevents a latch
    endcase
  end

  // Conditional operator in a continuous assignment
  assign zero = (result == 4'b0) ? 1'b1 : 1'b0;

endmodule

Synthesis builds an adder, a subtractor, an AND array and an OR array, followed by a 4 to 1 mux selected by op. The zero output is a 4-input NOR of result. It could also be written as assign zero = (result == 0);, since a comparison already produces 1 or 0.

Summary

In this tutorial, you learned:

  • Verilog has three conditional constructs: the conditional operator ? :, if-else and case
  • The conditional operator works in assign statements; if-else and case work only in procedural blocks
  • Each ? : and each simple if-else builds a 2 to 1 mux; nesting builds a mux tree
  • A case with distinct constant items builds a parallel mux with no priority
  • An if-else if chain keeps its priority when its conditions can overlap
  • Any non-zero condition is true, and an x condition behaves differently in if and ? :
  • In combinational logic, every path must assign every output, or synthesis infers a latch
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5