Familiar Syntax, Different Mental Model
if/else looks exactly like C:
if (condition) begin
// ... statements ...
end else begin
// ... statements ...
end
But the rules are different because Verilog isn't software. Two things to keep in mind:
if/elseonly lives inside a procedural block. You can't write a free-standingifat the module level.- What the
if/elsesynthesizes to depends on the block type. In a combinational block, it becomes a multiplexer or priority encoder. In a clocked block, it becomes a flip-flop with conditional update logic.
Inside a Combinational Block
The combinational always @(*) block re-runs whenever a, b, or c changes. The if/else chain picks one branch, assigns max, and the block finishes. Since max is always assigned (every path has an assignment), the synthesizer produces pure combinational logic - no latch.
Note that max is declared reg even though there's no flip-flop in the hardware. Same rule as always: anything assigned inside always must be reg.
The Latch Trap
This is the single most common bug in beginner combinational code:
// WRONG - infers a latch
always @(*) begin
if (enable)
out = data;
// no else! when `enable` is low, what does `out` do?
end
The synthesizer reads "when enable is low, out isn't assigned" and decides out has to remember its previous value. Remembering a value requires a storage cell, so the tool inserts a latch. Latches in synchronous designs cause timing problems, are hard to reset, and are almost never what you meant.
Two ways to fix it:
Both produce the same combinational hardware - a 2-to-1 mux. The "default at the top" pattern scales better when you have lots of conditional assignments to the same signal.
Inside a Clocked Block
Notice what's different from the combinational case:
- The block is
always @(posedge clk)- flip-flop territory. - Assignment uses
<=(non-blocking). - There's no
elsefor the "neither reset nor enable" case. That's fine. In a clocked block, when no branch fires, the flip-flop simply holds its previous value - which is exactly what a flip-flop physically does. No latch is inferred because the signal already is a register.
This is the one place where omitting an else is safe. Outside of clocked blocks, always handle every path.
Chained else if: A Priority Encoder
A chain of else if statements has implicit priority - earlier conditions outrank later ones:
requests[0] is the highest priority - if it's set, the grant is 0 regardless of what the higher-numbered bits do. The synthesizer turns the chain into a cascaded mux: check bit 0 first, then bit 1, then bit 2, then bit 3. Each level adds a small amount of delay.
If the conditions are mutually exclusive - say, decoding a one-hot input - a case statement (next doc) produces flatter, faster hardware than an else if chain. Use the case form when there's no genuine priority requirement.
if Without else in Clocked Code
A clocked block doesn't need an else because "hold the previous value" is the default. That's how you build enables:
always @(posedge clk) begin
if (load) target <= incoming;
// no else: when load is low, target keeps its value
end
That's a load-enabled register. Most pipeline registers, counters, and configuration registers use this pattern.
begin/end and Single Statements
Like C, you can omit begin/end for a single statement:
if (a) out = 1;
else out = 0;
For anything past one statement, use the block:
if (a) begin
out = 1;
flag = 1;
end else begin
out = 0;
flag = 0;
end
The two patterns mix freely. Style guides generally recommend always using begin/end to make adding a second statement painless.
What Comes Next
The next doc - Case Statement - covers case, which is the right tool for multi-way decoding (state machines, opcode dispatch, ROM tables). After that, for loops, which are subtly different from their software cousins because they're unrolled at elaboration time.
Frequently Asked Questions
How does an if statement work in Verilog?
if (cond) statement; runs statement when cond is non-zero. You can wrap multiple statements in begin ... end. Add else statement; for the alternative branch, or chain with else if (other_cond) .... if/else only exists inside procedural blocks - initial or always - not at the top level of a module.
What is an inferred latch in Verilog?
A latch the synthesis tool created without you asking, because your combinational always block didn't assign a signal in every path. The tool sees 'if a then out = 1' with no else, decides the unassigned case has to remember the previous value, and produces a latch. Latches are almost always wrong; the fix is to give every signal a default value at the top of the block or an explicit else.
How do you avoid inferred latches in Verilog?
In a combinational always @(*) block, make sure every output reg is assigned in every code path. The cleanest pattern is to set defaults at the top of the block and then override conditionally. The compiler usually warns when it infers a latch - treat the warning as an error.
What synthesizes from an if-else chain in Verilog?
A priority encoder. The first if has highest priority, the next else if is checked only if the first is false, and so on. In hardware that becomes a chain of muxes with the priority order baked in. If the conditions are mutually exclusive, a case statement with the same logic often synthesizes to flatter hardware and reads more clearly.