Module 59 min

Combinational Logic

assign and always @(*) - output depends only on inputs

Combinational logic means the output depends only on the inputs right now. There is no memory. If you know the inputs, you know the output - like a calculator that shows the answer the instant you type the numbers.

Two ways to write it

Verilog gives you two styles for the same thing. Use whichever reads more clearly for the job.

Style 1 - assign (dataflow)

verilog
// 2-to-1 mux with a continuous assignment
module mux2 (
  input  wire a,
  input  wire b,
  input  wire sel,
  output wire y
);
  assign y = sel ? a : b;
endmodule

Style 2 - always @(*) (behavioral)

When the logic has several cases, an always block reads better. For combinational logic, the sensitivity list must be @(*), which means "re-evaluate whenever any input changes."

verilog
module mux2 (
  input  wire a,
  input  wire b,
  input  wire sel,
  output reg  y      // reg because it is assigned in always
);
  always @(*) begin
    if (sel)
      y = a;
    else
      y = b;
  end
endmodule
Pro tip

Notice y is declared output reg here, but output wire in the assign version. Rule of thumb again: assigned inside always -> reg. Assigned with assign -> wire. It is purely about syntax, not about whether real hardware registers appear.

The golden rules for combinational always blocks

  1. Use @(*) for the sensitivity list - never list signals by hand, you will forget one.
  2. Use blocking assignment (=) inside, not <=. (Lesson 8 explains why.)
  3. Assign the output in every possible path through the block. If you do not, Verilog builds an unwanted memory element called a latch.
Watch out

This is the famous "inferred latch" bug. If your always @(*) sets y only in some branches and forgets others, the tool decides y must "remember" its old value, and silently adds a latch. The fix is simple: give every output a default value at the top of the block, before the if/case. The next lesson shows exactly how.