verilog-decisiones-multiplexores-if-else-case

Decisions in Verilog: multiplexers, if-else and case

  • 5 min

A multiplexer is a circuit that selects one of several inputs based on the value of a control signal.

In traditional programming, when we write an if, the processor evaluates a condition and jumps to one line of code or another. It is a change in the execution flow.

In an FPGA, as we have repeated (and will repeat ad nauseam), there is no flow. Everything happens at once.

So, how do we make decisions? How do we do “If A happens, do this; if not, do that”? The answer is physical: through Multiplexers.

Today we will see how classic control structures (if-else, case) translate into hardware and why you must be careful with priority.

The multiplexer: the digital switch

Before looking at the code, let’s visualize the component. A Multiplexer (MUX) is a digital switch. It has several data inputs, one output, and control inputs (selector).

If the selector is 0, it lets input A through. If it is 1, it lets input B through. One option or the other is not “executed”; both signals arrive at the selector gate, but only one crosses to the other side.

The if-else statement

In Verilog, the if-else statement is used inside always blocks (procedural logic).

When the synthesizer sees a combinational if-else, it infers a chain of multiplexers.

reg [3:0] output;

always @(*) begin
    if (selector == 1'b1) begin
        output = data_a;
    end else begin
        output = data_b;
    end
end
Copied!

This is equivalent to: assign output = (selector) ? data_a : data_b;

Priority logic

Here comes the catch. If we nest many if-else statements, we are creating a priority structure.

always @(*) begin
    if (reset) begin
        y = 0;
    end else if (enable) begin
        y = data;
    end else begin
        y = previous_value;
    end
end
Copied!

In hardware, this means:

  1. Check reset.
  2. ONLY IF it is not reset, check enable.

Physically, this creates multiplexers in a chain (cascade). If you have a very long if-else chain (e.g., 20 conditions), you are putting 20 multiplexers one after another. This increases the signal delay (Path Delay) and can make your design slow.

The if has a cost.

Each else if adds another level of logic that the signal must traverse. Use it when you truly need one signal to have priority over another (like a critical Reset).

The case statement

When we want to choose between many options (like in a menu), the case statement is much cleaner and often more efficient.

Unlike a priority chain of if, a case with mutually exclusive alternatives usually describes a multiplexer without explicit priority. The final structure depends on the conditions and the synthesizer’s optimization.

reg [7:0] result;
input [1:0] operation;

always @(*) begin
    case (operation)
        2'b00: result = a + b; // Addition
        2'b01: result = a - b; // Subtraction
        2'b10: result = a & b; // AND
        2'b11: result = a | b; // OR
        default: result = 8'b0; // IMPORTANT!
    endcase
end
Copied!

The importance of covering all alternatives

We already mentioned this in the article about always, but it is worth repeating. If in a case you do not cover all possible combinations (and remember, a 32-bit bus has 4 billion combinations), and you do not put a default, the FPGA will think: “Ah, if a value that is not on the list comes in, I have to keep the previous value”.

BOOM! Latch generated. Design broken.

In a combinational block, assign default values before the case or include a default so that all outputs are defined in every path.

Practical example: a simple ALU

Let’s create the heart of a processor: the ALU (Arithmetic Logic Unit). It is the module responsible for calculations. We will use a case to select which operation to perform.

module alu (
    input wire [7:0] a,
    input wire [7:0] b,
    input wire [2:0] opcode, // Operation code (3 bits -> 8 options)
    output reg [7:0] y
);

    // Define constants for code readability (good practice)
    localparam OP_ADD = 3'b000;
    localparam OP_SUB = 3'b001;
    localparam OP_AND = 3'b010;
    localparam OP_OR  = 3'b011;
    localparam OP_XOR = 3'b100;

    always @(*) begin
        case (opcode)
            OP_ADD:  y = a + b;
            OP_SUB:  y = a - b;
            OP_AND:  y = a & b;
            OP_OR:   y = a | b;
            OP_XOR:  y = a ^ b;
            // If the opcode is unknown, output 0
            default: y = 8'b0;
        endcase
    end

endmodule
Copied!

Which one should I use?

StatementHardware StructureRecommended Use
if-elseCascaded multiplexers (Priority)When one condition is critical and must “win” over others (e.g., Reset, Emergency stop). Or for simple binary decisions.
caseWide multiplexer (Parallel)When choosing one option among many at the same level (e.g., State machines, Instruction decoding, Truth tables).