Loading header...

Combinational Logic in Verilog

Combinational logic has no memory. Its outputs depend only on current inputs. In an FPGA, combinational logic usually maps to LUTs and routing.

Learning Objectives

By the end of this lesson, you should be able to:

  • describe combinational Verilog as hardware, not software flow;
  • write continuous assignments;
  • use always @* safely;
  • avoid accidental latch inference;
  • connect truth tables to HDL expressions and LUTs.

Continuous Logic

module majority3 (
    input  wire a,
    input  wire b,
    input  wire c,
    output wire y
);
    assign y = (a & b) | (a & c) | (b & c);
endmodule

This describes a combinational function. The synthesized result is logic, not a function call running on a processor.

Multiplexers

A multiplexer is one of the most common combinational structures.

assign y = sel ? b : a;

For a wider selector:

always @* begin
    y = 1'b0;
    case (sel)
        2'b00: y = d0;
        2'b01: y = d1;
        2'b10: y = d2;
        2'b11: y = d3;
    endcase
end

The default assignment is deliberate. It gives y a value even before the case choices are considered.

Combinational Blocks

Use combinational blocks when logic is easier to read procedurally.

always @* begin
    y = 1'b0;
    if (enable) begin
        y = a ^ b;
    end
end

SystemVerilog users can write always_comb, which gives stronger tool checking:

always_comb begin
    y = 1'b0;
    if (enable)
        y = a ^ b;
end

Accidental Latches

If a combinational output is not assigned in some branch, the tool may infer a latch to remember the old value.

always @* begin
    if (enable)
        y = a;
    // y is missing when enable is 0
end

Unless you intentionally need a latch, this is a bug. In synchronous FPGA design, latches are rarely what you want.

Worked Example: 2-to-4 Decoder

module decoder2to4 (
    input  wire [1:0] sel,
    output reg  [3:0] y
);
    always @* begin
        y = 4'b0000;
        case (sel)
            2'b00: y = 4'b0001;
            2'b01: y = 4'b0010;
            2'b10: y = 4'b0100;
            2'b11: y = 4'b1000;
        endcase
    end
endmodule

Every possible select value has a defined output.

Exercise

Write a 4-to-1 mux in Verilog:

  • inputs: d0, d1, d2, d3;
  • select: sel[1:0];
  • output: y.

Implement it twice:

  1. with a ternary/conditional expression;
  2. with always @* and case.

Then write a truth table and confirm every select value has exactly one source.

Common Mistakes

  • Missing default assignments in combinational blocks.
  • Using blocking/nonblocking assignments randomly.
  • Assuming HDL branch order implies time delay.
  • Forgetting that all right-hand-side inputs create hardware dependencies.
  • Writing a case statement without covering all choices.

Summary

Combinational FPGA logic maps naturally to LUTs. Use continuous assignments for simple expressions and combinational always @* / always_comb blocks for clearer decision logic. Always assign outputs on every path unless a latch is intentional.

Next: Exercise: Build Combinational Logic and a Testbench.

Further Reading

  • Verilator warnings guide
  • Yosys Verilog frontend documentation
  • Vendor HDL coding guidelines

Mind Map

mindmap root((Combinational Verilog)) Core concept Output depends on inputs assign equations always star covers inputs No memory Applications Applications Decoders Muxes Design checks ALU slices Checks All branches assign Truth table Common mistakes Common mistakes Incomplete case Blocking vs nonblocking confusion