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.
| Family | What it models | How it gets a value | Main types |
|---|---|---|---|
| Nets | A physical connection — a piece of wire between components | Must 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 |
| Variables | A storage location inside the simulator | Must 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 (usuallywire). - Left-hand side of an assignment inside
alwaysorinitial→ must be a variable (usuallyreg).
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
endmoduletime=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 awire. - 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
supply1andsupply0model permanent connections to power and ground, andtribehaves likewirebut 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
endmoduledata = 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:
| Value | Meaning | Where you see it |
|---|---|---|
0 | Logic low | Normal operation |
1 | Logic high | Normal operation |
x | Unknown | Uninitialized reg at time 0, bus contention, propagated don’t-knows |
z | High impedance | Undriven 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 likereg [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
endmoduleerror_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
endmodulebit 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
| Type | Family | Default width | States | Synthesizable | Typical use |
|---|---|---|---|---|---|
wire | Net | 1 bit | 4 | Yes | Connections, assign outputs |
reg | Variable | 1 bit | 4 | Yes | Anything written in always/initial |
integer | Variable | 32 bit, signed | 4 in expressions, used as 2-state | Yes (as 32-bit) | Loop indices, counters |
real | Variable | 64-bit float | n/a | No | Testbench math |
time | Variable | 64 bit, unsigned | 4 | No | Storing $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.
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