Verilog Data Types: wire, reg, Vectors and More

Every signal in a Verilog design must be declared with a data type, and the type you pick decides what the simulator does with it and what the synthesis tool is allowed to build from it. Most beginner errors in Verilog — “cannot assign to a wire”, “reg cannot be driven by a continuous assignment”, a comparison that mysteriously fails — are type errors, not logic errors. This chapter gives you the complete picture: nets vs variables, wire and reg, vectors, the four-state value system, integer, real, time, and signed arithmetic.

The one rule that explains everything: nets vs variables

Verilog splits all data types into two families, and almost every type-related compile error comes from mixing them up.

FamilyWhat it modelsHow it gets a valueMain types
NetsA physical connection — a piece of wire between componentsMust be driven continuously: by an assign statement, a module output port, or a gate. If nothing drives it, it floats to z.wire, tri, wand, wor, supply0, supply1
VariablesA storage location inside the simulatorMust be assigned inside a procedural block (always or initial). It keeps its value until the next assignment.reg, integer, real, time, realtime

From this one rule you can derive the two laws you will use every day:

  • Left-hand side of assign → must be a net (usually wire).
  • Left-hand side of an assignment inside always or initial → must be a variable (usually reg).

Here is both families working together: a multiplexer whose output is a driven net, and a testbench whose stimulus signals are variables that hold their values between assignments.

// A 2-to-1 multiplexer: the output is a net, driven continuously
module mux2 (
  input  wire a,
  input  wire b,
  input  wire sel,
  output wire y
);
  assign y = sel ? b : a;   // net: re-evaluated whenever a, b or sel changes
endmodule

// Testbench: stimulus signals are variables (reg)
module tb_nets_vs_variables;
  reg  a, b, sel;   // variables: hold their value until reassigned
  wire y;           // net: driven by the mux instance below

  mux2 u0 (.a(a), .b(b), .sel(sel), .y(y));

  initial begin
    $monitor("time=%0t  sel=%b a=%b b=%b -> y=%b", $time, sel, a, b, y);
    a = 1'b0; b = 1'b1; sel = 1'b0;   // y follows a
    #10 sel = 1'b1;                   // y now follows b
    #10 b   = 1'b0;                   // y updates on its own: it is driven, not stored
    #10 $finish;
  end
endmodule
time=0  sel=0 a=0 b=1 -> y=0
time=10  sel=1 a=0 b=1 -> y=1
time=20  sel=1 a=0 b=0 -> y=0
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpjn4z0mlf/nets_vs_variables.v:23: $finish called at 30 (1s)

Notice that at time 20 nobody assigns y, yet it changes. That is the essence of a net: it has no memory of its own and simply reflects whatever drives it.

wire: the default net

wire is by far the most common net type. Three facts worth knowing:

  • Ports are nets by default. If you write input a; without a type, you get a wire.
  • A wire may legally have multiple drivers. If two drivers disagree, the simulator resolves the value to x — this is how bus contention shows up in waveforms.
  • The special nets supply1 and supply0 model permanent connections to power and ground, and tri behaves like wire but documents that the signal is intentionally tri-stated.

reg: a variable, not a register

The single most damaging misconception in Verilog is that reg means “this becomes a flip-flop”. It does not. reg only means “this is a variable that can be assigned inside a procedural block”. Whether hardware storage is inferred depends entirely on how you write to it:

reg q, y, latch_q;

// 1. Clocked always block: reg becomes a flip-flop
always @(posedge clk)
  q <= d;

// 2. Complete combinational block: reg becomes pure logic, no storage
always @(*)
  y = a & b;

// 3. Incomplete combinational block: reg becomes an unintended latch!
always @(*)
  if (en) latch_q = d;   // no else: value must be remembered when en=0

Case 2 is the proof: y is declared reg but synthesizes to a plain AND gate with zero storage. Case 3 is a classic interview question — the missing else forces the tool to infer a level-sensitive latch so the old value survives, which is almost never what you wanted. Say this clearly in an interview: reg is a simulation construct; registers come from clocked assignments, not from the keyword.

Vectors: buses, part-selects and concatenation

By default wire and reg are 1-bit (scalar). A range turns them into a vector — a multi-bit bus:

wire [7:0]  data_bus;    // 8 bits, data_bus[7] is the MSB
reg  [31:0] result;      // 32 bits
wire [0:7]  big_endian;  // legal, but bit 0 is now the MSB - avoid mixing styles

The convention [MSB:LSB] with the high index on the left ([7:0]) is standard in industry. Stick to it throughout a project; mixing [7:0] and [0:7] declarations is a reliable source of reversed-bus bugs. From a vector you can pull a single bit (data[3]), a contiguous slice (data[7:4]), or glue pieces together with the concatenation operator { }:

module tb_vectors;
  reg [7:0]  data;          // 8-bit vector, bit 7 is the MSB
  reg [3:0]  upper, lower;
  reg [15:0] bus;

  initial begin
    data  = 8'hA5;              // 1010_0101
    upper = data[7:4];          // part-select: top nibble
    lower = data[3:0];          // part-select: bottom nibble
    bus   = {data, lower, 4'b1111};  // concatenation builds wider vectors

    $display("data       = %b (hex %h, decimal %0d)", data, data, data);
    $display("data[7:4]  = %b", upper);
    $display("data[3:0]  = %b", lower);
    $display("data[0]    = %b  (LSB)", data[0]);
    $display("bus        = %b", bus);
    $finish;
  end
endmodule
data       = 10100101 (hex a5, decimal 165)
data[7:4]  = 1010
data[3:0]  = 0101
data[0]    = 1  (LSB)
bus        = 1010010101011111
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpvr0649d_/vectors.v:17: $finish called at 0 (1s)

Replication is concatenation’s shorthand cousin: {4{2'b10}} produces 10101010, and the sign-extension idiom {{8{x[7]}}, x} widens an 8-bit value to 16 bits. An array of vectors declares a memory: reg [7:0] mem [0:255]; is 256 locations of 8 bits each — note the second range sits after the name.

Four values, not two

Every bit of a wire or reg carries one of four values, which is why Verilog is called a 4-state language:

ValueMeaningWhere you see it
0Logic lowNormal operation
1Logic highNormal operation
xUnknownUninitialized reg at time 0, bus contention, propagated don’t-knows
zHigh impedanceUndriven wire, disabled tri-state driver

A reg starts simulation as x; an undriven wire sits at z. Seeing red x values spreading through a waveform usually points at a missing reset or an unconnected port — learn to read them as diagnostics, not noise.

integer, real and time

These variable types exist mainly for testbenches and behavioural code:

  • integer — a 32-bit signed 2-state variable. Ideal for loop counters and error counts. It is synthesizable (tools treat it as a signed 32-bit reg), but RTL style prefers explicit widths like reg [5:0].
  • real — a double-precision floating-point number. Not synthesizable, ever. Use it for checking analog-ish quantities, averages, or expected values in a testbench.
  • time — a 64-bit unsigned variable sized to hold simulation timestamps from $time. 32 bits overflow quickly in long simulations, which is exactly why this type exists.
`timescale 1ns/1ns
module tb_int_real_time;
  integer error_count;   // 32-bit signed, 2-state: for counting in testbenches
  real    half_vdd;      // double-precision float: never synthesizable
  time    t_capture;     // 64-bit unsigned: holds simulation time

  initial begin
    error_count = -3;              // integers hold negative values naturally
    half_vdd    = 3.3 / 2.0;       // real arithmetic keeps the fraction
    #25 t_capture = $time;         // sample the simulator clock

    $display("error_count = %0d", error_count);
    $display("half_vdd    = %f", half_vdd);
    $display("t_capture   = %0t", t_capture);
    $display("int result of 3.3/2.0 would be %0d", (33/20)); // integer division truncates
    $finish;
  end
endmodule
error_count = -3
half_vdd    = 1.650000
t_capture   = 25
int result of 3.3/2.0 would be 1
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpkhtretjj/int_real_time.v:16: $finish called at 25 (1ns)

Signed vs unsigned: the silent bug factory

Plain wire and reg are unsigned. Add the signed keyword and the same bits are interpreted in two’s complement. The bits never change — only the arithmetic and comparison rules do:

module tb_signed_unsigned;
  reg        [7:0] u;   // unsigned by default
  reg signed [7:0] s;   // same width, signed interpretation

  initial begin
    u = 8'b1111_1100;   // same bit pattern in both
    s = 8'b1111_1100;

    $display("bit pattern 11111100:");
    $display("  as unsigned u = %0d", u);
    $display("  as signed   s = %0d", s);

    if (u > 10) $display("  u > 10 is TRUE  (compared as 252)");
    else        $display("  u > 10 is FALSE");

    if (s > 10) $display("  s > 10 is TRUE");
    else        $display("  s > 10 is FALSE (compared as -4)");

    $display("  s >>> 1 = %b (arithmetic shift keeps the sign)", s >>> 1);
    $display("  u >>  1 = %b (logical shift fills with 0)", u >> 1);
    $finish;
  end
endmodule
bit pattern 11111100:
  as unsigned u = 252
  as signed   s = -4
  u > 10 is TRUE  (compared as 252)
  s > 10 is FALSE (compared as -4)
  s >>> 1 = 11111110 (arithmetic shift keeps the sign)
  u >>  1 = 01111110 (logical shift fills with 0)
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpwxvys7qt/signed_unsigned.v:21: $finish called at 0 (1s)

Two traps to remember. First, if a single unsigned operand appears anywhere in an expression, Verilog evaluates the whole expression as unsigned — one stray unsigned wire silently breaks your signed comparison. Second, >>> (arithmetic right shift) replicates the sign bit only when the operand is declared signed; on an unsigned operand it behaves like a plain >>.

Quick reference

TypeFamilyDefault widthStatesSynthesizableTypical use
wireNet1 bit4YesConnections, assign outputs
regVariable1 bit4YesAnything written in always/initial
integerVariable32 bit, signed4 in expressions, used as 2-stateYes (as 32-bit)Loop indices, counters
realVariable64-bit floatn/aNoTestbench math
timeVariable64 bit, unsigned4NoStoring $time

If you remember nothing else: nets are driven, variables are assigned; reg does not mean register; declare widths explicitly; and never let an unsigned operand sneak into signed arithmetic. These four habits eliminate the majority of Verilog type errors you will meet in coursework, interviews and real projects.

Quick check

Why does assign y = a & b; fail to compile when y is declared as reg?

A continuous assign drives a net, and reg is a variable, not a net. Variables can only be written inside procedural blocks (always/initial). Declare y as wire, or move the logic into always @(*) and keep it a reg.

Does declaring a signal as reg create a flip-flop in hardware?

No. reg only permits procedural assignment. A flip-flop is inferred when the reg is assigned in a clocked block such as always @(posedge clk). The same reg assigned in a complete always @(*) block becomes pure combinational logic, and an incomplete combinational block (missing else or a case item) infers an unintended latch.

A reg [7:0] s holds 8'b1111_1100. What does s > 10 evaluate to, and how does signed change it?

Unsigned by default, the bits mean 252, so s > 10 is true. Declared reg signed [7:0], the same bits mean -4 in two’s complement, so the comparison is false. And if any unsigned operand appears in the expression, the whole comparison reverts to unsigned.

What is the difference between x and z, and where does each come from?

x means unknown — an uninitialized reg, two drivers fighting on one wire, or a propagated unknown. z means high impedance — a wire that nothing drives, such as a disabled tri-state buffer. An undriven wire floats to z; an unreset reg starts at x.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD