Skip to article

Modules, Signals, and Numbers

`wire` vs `reg` in Verilog

What wire and reg mean in classic Verilog, how each may be driven, and why inferred combinational logic, latches, and flip-flops depend on behavior rather than the keyword.

4 min read · Updated


In classic Verilog, wire declares a net and reg declares a procedural variable. Those keywords mainly control how a signal may be driven in the language. Neither keyword, by itself, proves whether synthesized hardware contains storage.

Use a wire for a connection driven by a continuous assignment, primitive output, or module output. Use a reg for a value assigned by procedural statements in an initial, always, task, or function context. For synthesizable RTL, the behavior and assignment coverage of that procedural code determine whether the hardware is combinational, a latch, or a flip-flop.

Quick comparison

Question wire net reg procedural variable
Typical driver assign, primitive output, module output Assignment inside procedural code
Can the declaration alone imply storage? No No
Multiple writers/drivers Multiple net drivers are legal and resolved; disagreement may produce x Multiple procedural writers can simulate with races but are poor RTL and generally not synthesizable
Typical synthesis role A connection between driver and loads Combinational result, latch, or flip-flop depending on the complete process
Does it keep an old value? A net continuously reflects its resolved drivers Only when the procedural behavior requires retention

Special net types such as wand and wor change the resolution function. They are not required merely to have more than one driver on a net.

The behavior creates the hardware

These two descriptions both infer an AND function with no storage:

wire y_wire;
assign y_wire = a & b;
reg y_comb;
always @(*) begin
  y_comb = a & b;
end

The second output is a reg because it is assigned procedurally. Its assignment is complete on every evaluation, so it does not need to retain an older value.

An incomplete procedural assignment is different:

reg y_latched;
always @(*) begin
  if (enable)
    y_latched = data;
end

When enable is low, the code requires y_latched to keep its previous value. Synthesis can therefore infer a level-sensitive latch even though the sensitivity list is @(*) and there is no clock edge.

An edge-triggered process commonly infers a flip-flop:

reg y_registered;
always @(posedge clk) begin
  y_registered <= a & b;
end

The important questions are: what event activates the process, is every procedural value assigned on every relevant path, and what value must persist between activations?

Predict before running

The example compares a continuously driven net, a completely assigned combinational variable, and an edge-triggered variable. Input changes occur away from the rising edge so the sampling point is unambiguous.

Before running it, predict which outputs react between edges and which output changes only after a rising edge.

`timescale 1ns/1ps

module wire_example (
  input  wire a,
  input  wire b,
  output wire y_out
);
  assign y_out = a & b;
endmodule

module variable_combinational (
  input  wire a,
  input  wire b,
  output reg  y_out
);
  always @(*) begin
    y_out = a & b;
  end
endmodule

module variable_registered (
  input  wire clk,
  input  wire a,
  input  wire b,
  output reg  y_out
);
  always @(posedge clk) begin
    y_out <= a & b;
  end
endmodule

module tb;
  reg a, b, clk;
  wire y_wire, y_comb, y_registered;

  wire_example            u_wire(.a(a), .b(b), .y_out(y_wire));
  variable_combinational  u_comb(.a(a), .b(b), .y_out(y_comb));
  variable_registered     u_reg(.clk(clk), .a(a), .b(b), .y_out(y_registered));

  always #5 clk = ~clk;

  initial begin
    clk = 1'b0; a = 1'b1; b = 1'b1;
    #1;
    $display("t=%0t a=%b y_wire=%b y_comb=%b y_registered=%b", $time, a, y_wire, y_comb, y_registered);
    if (y_wire !== 1'b1 || y_comb !== 1'b1 || y_registered !== 1'bx) begin $display("FAIL T1"); $finish; end

    a = 1'b0; #1;
    $display("t=%0t a=%b y_wire=%b y_comb=%b y_registered=%b", $time, a, y_wire, y_comb, y_registered);
    if (y_wire !== 1'b0 || y_comb !== 1'b0 || y_registered !== 1'bx) begin $display("FAIL T2"); $finish; end

    @(posedge clk); #1;
    $display("t=%0t a=%b y_wire=%b y_comb=%b y_registered=%b", $time, a, y_wire, y_comb, y_registered);
    if (y_registered !== 1'b0) begin $display("FAIL T6"); $finish; end

    @(negedge clk); a = 1'b1; #1;
    $display("t=%0t a=%b y_wire=%b y_comb=%b y_registered=%b", $time, a, y_wire, y_comb, y_registered);
    if (y_wire !== 1'b1 || y_comb !== 1'b1 || y_registered !== 1'b0) begin $display("FAIL T11"); $finish; end

    @(posedge clk); #1;
    $display("t=%0t a=%b y_wire=%b y_comb=%b y_registered=%b", $time, a, y_wire, y_comb, y_registered);
    if (y_registered !== 1'b1) begin $display("FAIL T16"); $finish; end

    $display("PASS");
    $finish;
  end
endmodule

Expected output:

t=1000 a=1 y_wire=1 y_comb=1 y_registered=x
t=2000 a=0 y_wire=0 y_comb=0 y_registered=x
t=6000 a=0 y_wire=0 y_comb=0 y_registered=0
t=11000 a=1 y_wire=1 y_comb=1 y_registered=0
t=16000 a=1 y_wire=1 y_comb=1 y_registered=1
PASS

y_registered samples a & b on the rising edges at 5 ns and 15 ns. The testbench waits one additional nanosecond before observing it, so the lines printed at 6 ns and 16 ns are post-update observations, not the capture times themselves. Between those edges, y_wire and y_comb react to a, while y_registered holds its sampled value.

Practical rules

  1. Use wire for classic-Verilog connections driven continuously or by another module.
  2. Use reg when classic Verilog requires a procedural assignment target.
  3. Determine storage by reading the entire process: event control, assignment coverage, and hold behavior—not only the declaration or sensitivity list.
  4. Keep one intentional procedural writer per synthesizable variable. Multiple procedural writers create races and are normally rejected for synthesis.
  5. Use non-blocking assignment for state-holding variables in edge-triggered RTL and blocking assignment for combinational calculations.

SystemVerilog’s logic, always_comb, and always_ff make these intentions more explicit, but they do not remove the need to understand the inferred hardware.

Sources and verification

Example provenance: SkillLift Labs authored the example for this tutorial. tests/scripts/tutorial-verified-examples.test.ts compiles it in Verilog-2005 mode, reproduces the published output, and independently checks the expected combinational and edge-sampled values, including capture-versus-observation timing. The same test exercises the article-to-runner source split. Automation verifies those stated properties; human technical review for indexing remains a separate gate.