Skip to content
code0.HDL

Nets, variables and the combinational always

Tell nets from variables, write a multiplexer twice, as an assign with ?: and as an always @* with case, see why every path must assign the output, and use blocking = inside combinational blocks.

In this chapter

  • Say whether a signal is a net or a variable, which assignment may target it, and what reg does and does not mean
  • Write a multiplexer as an assign with ?:, as an always @* block with if/else and with case, and confirm by an exhaustive comparison that the three are the same circuit
  • Spot a combinational block whose paths do not all assign the output, predict the held value it prints, and explain why a latch results
  • Read the l-value error Icarus gives for a procedural assignment to a net and apply either repair
Concepts in this chapter
  • MUX, always @*, every branch assigned
Prerequisite concepts

1. A multiplexer picks one of its inputs

A multiplexer has data inputs, a select input and one output. The select decides which data input reaches the output; the others are ignored. Nothing is stored: change the select and the output switches at once. Operate the two-input version below until the rule is obvious.

Concept experiment · mux2

A browser model of the circuit, not a compiled design. The real toolchain runs in the examples.

Inputs

Outputs

y
0
Truth table
selabycurrent inputs
0000◂ current inputs
0010
0101
0111
1000
1011
1100
1111

Check

In the mux2 circuit, sel is 1, a is 0 and b is 1. What is y?

This chapter writes that multiplexer in Verilog as an assign and as procedural blocks, then runs all three forms side by side. The forms assign different kinds of signal, so the kinds come first.

2. Nets and variables are two kinds of signal

Verilog signals come in two kinds, nets and variables. They differ in how they receive a value and in whether they keep it.

KindTypical declarationHow it gets a valueDoes it keep the value?
Netwire. A port is a wire unless you say otherwiseassign, or the output of another moduleNo. It always shows whatever drives it
Variablereg, integerAssignment statements inside initial or alwaysYes. It remembers the last value assigned

A good first picture: a net is a wire in the circuit, and a variable is a value that the simulation keeps between the procedural assignments that write it. That is not the same as hardware storage: a variable assigned in always @* describes combinational logic, and only a variable assigned on a clock edge becomes a register, as Registers and nonblocking assignment shows.

Note. The name reg suggests a hardware register, but declaring something reg does not create a flip-flop. The hardware you get is decided by how the signal is assigned, not by the keyword that declares it.

3. Continuous and procedural assignment

Matching the two kinds of signal, there are two kinds of assignment.

AssignmentSyntaxWhere it appearsTarget
Continuousassign target = expression;Module bodyA net
Proceduraltarget = expression; or target <= expression;Inside initial or alwaysA variable

A continuous assignment is a connection that holds at every moment. A procedural assignment happens at one particular moment, and its target has to keep the value until the next assignment. A net cannot keep a value, so a procedural assignment to a net is a compile error.

The opposite case, assign to a variable, depends on the language version.

Language version (iverilog option)assign to a reg
Verilog-2005 (-g2005)An error: reg y; cannot be driven by primitives or continuous assignment.
SystemVerilog (-g2012)Allowed, as long as nothing else drives it

The examples in this course are compiled with -g2012.

Tip. SystemVerilog's logic is a four-state data type, not a replacement for the net/variable distinction: logic y; declares a variable, while wire logic y; declares a net. This course uses wire and reg to keep that distinction visible.

4. The first way: assign with ?:

The conditional operator ?: is the multiplexer written as an expression. cond ? x : y is x when the condition is true and y otherwise, and the whole thing is a wire.

module mux2 (
  input        sel,
  input  [3:0] a,
  input  [3:0] b,
  output [3:0] y
);
  assign y = sel ? b : a;
endmodule

Both branches are evaluated at the context width of Literals, signed values and expression width, so a and b should be the same width as y. For a two-way choice this is the shortest and clearest form.

5. The second way: always @* with case

When there are several choices, a case inside a procedural block reads better than nested ?:. always @* means "run this block again whenever any signal it reads changes", which is exactly the behavior of combinational logic; it is how you write combinational logic as procedural code. The output must be declared reg, because only a variable can be assigned inside always.

module mux4 (
  input      [1:0] sel,
  input      [3:0] a, b, c, d,
  output reg [3:0] y
);
  always @* begin
    case (sel)
      2'd0:    y = a;
      2'd1:    y = b;
      2'd2:    y = c;
      default: y = d;
    endcase
  end
endmodule

The word reg here does not create storage. The block recomputes y from the current inputs every time one of them changes, so y is a combinational output exactly like the assign version. What makes a variable a register is how it is assigned, which is the next chapter's subject.

Note. The examples compile with iverilog -g2012. always @* is Verilog-2001 and is what this course uses. SystemVerilog adds always_comb for the same purpose; it is not used here.

Check

In mux4, sel is 2'd2. Which input reaches y?

Your answer

6. Three ways, one multiplexer, compared by simulation

A third legal form is an if/else if chain inside always @*. Mux4.v writes the same 4-to-1 multiplexer of 1-bit inputs all three ways: mux4_assign with nested ?:, mux4_if with an if/else chain and mux4_case with a case and a default. There is no clock anywhere in the file, yet two of the three outputs are declared output reg.

Mux4.v
// One 4-to-1 multiplexer, written three legal ways.
module mux4_assign (
  input  [1:0] sel,
  input        a, b, c, d,
  output       y
);
  assign y = (sel == 2'd0) ? a :
             (sel == 2'd1) ? b :
             (sel == 2'd2) ? c : d;
endmodule

module mux4_if (
  input      [1:0] sel,
  input            a, b, c, d,
  output reg       y
);
  // reg here is a variable, not a flip-flop: there is no clock.
  always @* begin
    if      (sel == 2'd0) y = a;
    else if (sel == 2'd1) y = b;
    else if (sel == 2'd2) y = c;
    else                  y = d;
  end
endmodule

module mux4_case (
  input      [1:0] sel,
  input            a, b, c, d,
  output reg       y
);
  always @* begin
    case (sel)
      2'd0:    y = a;
      2'd1:    y = b;
      2'd2:    y = c;
      default: y = d;
    endcase
  end
endmodule

Check

Mux4.v declares y as output reg in mux4_if and mux4_case. How many flip-flops does the whole file describe?

Your answer

Tb.v places all three modules on the same inputs. It first holds a=0 b=1 c=1 d=0 and steps sel from 0 to 3, printing one SEL line per value with the three outputs side by side. Then it runs through all 64 combinations of sel and the four data bits, compares mux4_if and mux4_case against mux4_assign with !==, and prints how many combinations disagreed.

Tb.v
module tb;
  reg  [1:0] sel;
  reg        a, b, c, d;
  wire       y_assign, y_if, y_case;
  integer    i, mismatches = 0;

  mux4_assign u_assign (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y_assign));
  mux4_if     u_if     (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y_if));
  mux4_case   u_case   (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y_case));

  initial begin
    // One data pattern, every select value: a=0 b=1 c=1 d=0.
    {a, b, c, d} = 4'b0110;
    for (i = 0; i < 4; i = i + 1) begin
      sel = i;
      #1 $display("SEL %0d assign=%b if=%b case=%b", sel, y_assign, y_if, y_case);
    end
    // Every combination of sel and data: 4 x 16 = 64.
    for (i = 0; i < 64; i = i + 1) begin
      {sel, a, b, c, d} = i;
      #1;
      if (y_if !== y_assign || y_case !== y_assign) mismatches = mismatches + 1;
    end
    $display("COMPARED 64 combinations mismatches=%0d", mismatches);
    $finish;
  end
endmodule

Predict before you run. For each SEL line, which input is selected and what is its value? Do the three columns ever differ?

Check

With a=0 b=1 c=1 d=0, what does the SEL 3 line print?

Your answer

Check the result. The SEL lines read 0, 1, 1, 0, the values of a, b, c, d, and the three columns agree on every line. The last line is COMPARED 64 combinations mismatches=0: over all 64 binary input combinations, the three descriptions agree. This comparison does not test select values containing x or z.

Change one thing. In mux4_case, delete the line default: y = d; and run again. Predict the SEL 3 line first.

Check

Without the default, what does the SEL 3 line print?

Your answer

The run now ends with mismatches=8. During the 16 combinations with sel = 3, mux4_case keeps the 1 it had for the last sel = 2 combination, so it is wrong exactly where d is 0. Put the default back before you go on. Why a held value is a problem is the next section.

7. Every path through the block must assign the output

Suppose the case in mux4 had only the 2'd0 and 2'd1 branches and no default. For sel equal to 2 or 3, nothing in the block assigns y. In simulation y then keeps whatever it had before, which means the description now needs memory of a previous value, and a combinational block was not supposed to have any. A synthesis tool reads it the same way and has to build a storage element, a latch, to hold the old value; most tools warn about it, and the circuit is not the multiplexer you meant. Icarus itself prints no warning; the only sign is the held value in the output.

LatchMux.v is a 4-to-1 multiplexer whose case has branches for 0, 1 and 2 but none for 3 and no default. Tb.v prints the multiplexer's y next to full, the value a complete multiplexer would give, after each of five input changes.

LatchMux.v
// The case has no branch for sel = 3 and no default.
module mux4_missing (
  input      [1:0] sel,
  input            a, b, c, d,
  output reg       y
);
  always @* begin
    case (sel)
      2'd0: y = a;
      2'd1: y = b;
      2'd2: y = c;
    endcase
  end
endmodule
Tb.v
module tb;
  reg  [1:0] sel;
  reg        a = 0, b = 0, c = 0, d = 0;
  wire       y;
  // What a complete multiplexer would output, for comparison.
  wire       full = (sel == 2'd0) ? a : (sel == 2'd1) ? b : (sel == 2'd2) ? c : d;

  mux4_missing dut (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y));

  task show(input integer n);
    #1 $display("STEP %0d sel=%0d a=%b d=%b -> y=%b full=%b", n, sel, a, d, y, full);
  endtask

  initial begin
    sel = 3; d = 1;   show(1);
    sel = 0; a = 1;   show(2);
    sel = 3; d = 0;   show(3);
    a = 0;            show(4);
    sel = 1;          show(5);
    $finish;
  end
endmodule
StepInputs setA complete mux gives
1sel=3, d=11
2sel=0, a=11
3sel=3, d=00
4a=0, still sel=30
5sel=1, b=00

Predict before you run. Write the y column yourself. At step 1 nothing has ever assigned y; at steps 3 and 4 the block runs again, because an input it reads changed, but no branch matches.

Check

At STEP 3, sel is 3 again and d is 0. What does y print?

Your answer

Check the result.

LineyfullWhat it shows
STEP 1x1y has never been assigned, so it holds its starting value x
STEP 211A matching branch: y follows a
STEP 3, STEP 410The held value: y remembers STEP 2 although d and a are 0
STEP 500A matching branch again

Fix it. Add default: y = d; after the 2'd2 branch and run again. Every y now equals full, STEP 1 included: no path leaves y unassigned, so nothing has to be remembered.

The rule is simple to apply. Either give the case a default branch, or assign the output once before the case so that every path has a value. The same applies to if without else.

Check

Which of these infers a latch in a combinational block?

Your answer

8. Blocking = is the assignment for combinational blocks

Inside a combinational block use the blocking assignment =. It updates its target at once, so the next statement in the block already reads the new value. That is what a chain of combinational steps needs: compute an intermediate, then use it.

always @* begin
  tmp = a & b;      // tmp has its new value from here on
  y   = tmp | c;    // reads the new tmp
end

The other assignment, <=, delays the update and is for registers in clocked blocks. Mixing the two in one block is where most beginners' bugs come from; Registers and nonblocking assignment draws the line.

Check

In the block above, a = 1, b = 1 and c = 0. After the block runs, what is y?

9. Run a block that fails on purpose

The next example fails to compile on purpose. Majority.v is a majority circuit: its output is 1 when at least two of its three inputs are 1. It is written as an always @* block, but its output y is declared only as output y, which makes it a net (wire). Yet the code assigns y inside the block. Decide before running: does this fail when iverilog compiles it, or when vvp simulates it?

Majority.v
module majority (
  input  a,
  input  b,
  input  c,
  output y
);
  // y is a net, so a procedural block cannot assign it.
  always @* begin
    y = (a & b) | (a & c) | (b & c);
  end
endmodule

Practice this concept

Use this concept in a short Verilog problem as your next step.

Check

At which stage does Majority.v fail?

Your answer

Check the result. Press Run and read the two lines of the message.

ItemContent
Faulty codey = ... inside an always @* block, while y is a net
Failing stageCompilation in iverilog. vvp never runs
Error messagey is not a valid l-value in majority. followed by y is declared here as wire.
FixDeclare output reg y, or replace the block with assign y = ...;

An l-value is anything that may appear on the left of an assignment. The second line of the message points at the place where y was declared a wire, so the declaration is the first thing to check.

Then change one thing: make it output reg y and run again. It compiles now. Nothing is printed, because the file has no testbench; the exit code of 0 is all you get. The other repair is to delete the block and write the same expression after assign. Both fixes produce the same majority circuit. For logic that is a single expression, as here, assign is easier to read. For logic with several branches, a reg target in an always @* block is usually clearer.

10. Common mistakes

10.1 Assigning a net from procedural code

// Wrong: y is a wire
output y;
always @* y = a & b;

// Right, option 1: declare a variable
output reg y;
always @* y = a & b;

// Right, option 2: use a continuous assignment
output y;
assign y = a & b;

Anything you assign inside always or initial must be a variable: reg, integer, or, with -g2012, logic. The error message for this mistake is ... is not a valid l-value.

10.2 Reading a variable before assigning it

reg [7:0] total;
initial $display("%d", total);   // prints x, not 0

A variable starts as x, not 0. When you see x in the output, check whether the variable was assigned before it was used, and whether any input of the calculation contains x or z.

10.3 Reading an output right after changing an input

data = 8'hFF;
$display("%b", high);   // may still show the old value or x
#1;
$display("%b", high);   // shows the new value

At the instant an input changes, the assign statements that read it may not have been re-evaluated yet. In a testbench, change the input, wait one time unit with something like #1, and then read the output.

11. Summary

ElementMeaning
net (wire) and variable (reg, integer)a net is driven by assign and keeps nothing; a variable is assigned in procedural blocks and keeps its value
assign y = sel ? b : a;a continuous assignment: a two-way multiplexer as a wire
always @* … casecombinational logic as procedural code; the target is reg
?:, if/else, case with defaultthree legal forms of the same multiplexer; an exhaustive comparison finds 0 mismatches
rega variable, not storage; the assignment style decides
every path assignsotherwise the old value must be kept, and that is a latch
= in always @*blocking: the next statement sees the new value
y = … on a neta compile error: not a valid l-value

12. Check your understanding

  1. Why must the target of an assignment inside an always block be a variable?

    Show answer

    A procedural assignment happens at one moment, and the target has to keep the value until the next assignment. A net cannot keep a value; it always shows whatever drives it, so it cannot be the target of a procedural assignment.

  2. output reg [3:0] y is assigned inside always @*. Does the reg make y a register?

    Show answer

    No. The block recomputes y from the current inputs whenever one of them changes, so y is a combinational output. A variable becomes a register only when it is assigned on a clock edge; the keyword says nothing about storage.

The Practice problem select-op applies this chapter: a 2-bit sel chooses which of four bitwise operations of two 4-bit inputs drives y, with no clock. Its starter is a case skeleton inside always @* whose default gives y 0; fill in the four operations so that every select value, and every path through the block, assigns y.

Checkpoint

Answer the checks in this chapter

Sources

Licences and notices for the sources this site follows