Verilog · lesson 3

Wires, Regs, and the Four Values

Why Verilog has two kinds of signal instead of one, the 0/1/x/z value set every signal actually carries, and bit and part selects on a vector.

10 min read · Free, no login

Why Verilog has two kinds of signal

Lesson 2's half adder declared its ports wire; its testbench declared a and b as reg. These are Verilog's two data kinds, and the distinction trips up nearly every beginner at least once, because the names suggest something they do not quite mean. A wire models a physical connection โ€“ it has no value of its own and takes on whatever value is being driven onto it, continuously, by something else: a gate, an assign statement, or a module output port. A reg models a variable that holds a value until something explicitly changes it โ€“ it is assigned inside a procedural block (an initial or always block, covered properly starting in lesson 5) and keeps that value between assignments.

The name reg causes real confusion, because it does not mean "this becomes a hardware register" (a flip-flop). A reg declared inside a combinational always block, with no clock involved at all, synthesizes to plain combinational logic, not storage โ€“ lesson 6 shows exactly this. The rule that actually matters is procedural: a signal assigned inside an initial or always block must be declared reg; a signal driven by assign, or connected to a module's output port from outside, must be declared wire. Mixing these up is a compile error, not a warning, which is at least honest about catching the mistake immediately. (SystemVerilog's logic type, covered in lesson 14, removes this distinction entirely by allowing either kind of assignment on the same declaration โ€“ but plain Verilog, the subject of lessons 1 through 13, still enforces it.)

Every signal is one of four values, not two

A software boolean has two states. A real wire on a real chip has more than that, and Verilog models every single bit of every signal, of every kind, as one of four values:

The four-value logic system
ValueMeaning
0Logic zero
1Logic one
xUnknown โ€” could be 0, could be 1, the simulator cannot tell (an uninitialized register, or a genuine electrical conflict such as two drivers fighting over the same wire)
zHigh impedance โ€” nothing is driving this signal at all (an undriven wire, or a tri-state bus with every driver turned off)

These are not an edge case to be aware of occasionally โ€“ they are the actual value set every signal carries, all the time, and a simulator will print exactly what it computes. Declaring a register and never assigning it, or reading a wire with nothing driving it, are both completely ordinary situations, and Verilog has a defined answer for both rather than an error:

byte_val         = 10100101 (hex a5, decimal 165)
byte_val[7]      = 1   (MSB, bit select)
byte_val[3:0]    = 0101   (low nibble, part select)
floating wire    = z   (no driver at all)
undriven_reg     = x   (declared, never assigned)

A wire with nothing driving it reads as z; a reg that was declared but never assigned reads as x. Both are correct, useful signals from the simulator, not bugs in the simulator โ€“ an x appearing somewhere it should not is one of the most common and most useful things a testbench actually catches, and lesson 10 builds on exactly that fact to write a self-checking testbench.

Vectors, literals, and selecting part of a signal

A signal is not limited to one bit. reg [7:0] byte_val declares an 8-bit vector, with bit 7 as the most significant bit and bit 0 as the least significant โ€” this is the standard convention this course follows throughout, matching how a number is normally written and read. A literal like 8'hA5 states its own width (8 bits), its own base (h for hexadecimal; b for binary, d for decimal, and o for octal all work the same way), and its value โ€” always write the width and base explicitly rather than relying on a bare number, since an un-sized literal's width is decided by context in a way that is easy to get wrong and very easy to simulate correctly while synthesizing incorrectly.

A single bit of a vector is read or written with a bit select, byte_val[7]; a contiguous range is read or written with a part select, byte_val[3:0]. Both are demonstrated directly in the output above: byte_val[7] reads the top bit of 10100101, which is 1; byte_val[3:0] reads the bottom nibble, 0101. A part select's range must be written high-bit-first to low-bit-first, matching how the vector itself was declared โ€” byte_val[0:3] on an [7:0] vector is not a reversed read, it is simply illegal.

A literal can itself contain x or z bits, and arithmetic or assignment involving them produces exactly the result you would expect from the four-value rules โ€” any operation touching an unknown bit produces an unknown result in that bit, which is the simulator correctly refusing to guess:

byte_val w/ x    = x0000001

The top bit was assigned x explicitly here, and the simulator carries it through exactly as written โ€” nothing resolves an x into a guessed 0 or 1 on its own.

Two kinds of equality

Because x and z are real values, Verilog needs two different ideas of "equal." The ordinary == operator is logical equality: if either operand has any x or z bit anywhere, the entire comparison's result is itself x (unknown) โ€” not true, not false, genuinely unknown, which an if statement then treats as false. The === operator is case equality: it compares every bit literally, including x and z bits, and always returns a definite 1 or 0.

=== : 1
==  : x

Comparing the same 8-bit value containing an x bit against itself: === correctly reports a match, because it is comparing the literal bit patterns; == reports x, because logical equality refuses to claim a match through an unknown bit, even when both sides happen to carry the identical unknown. === and its partner !== are simulation-only constructs with no synthesis meaning at all โ€” they exist specifically for testbenches that need to check for x or z directly, which is exactly how lesson 10's self-checking testbenches will use them.

Common mistakes

Declaring a signal wire and then assigning it inside an always block is a compile error, not a warning โ€” the fix is almost always to change the declaration to reg, not to restructure the logic. Using == in a testbench to check for an uninitialized or error value is the second common mistake: if the very thing being checked for is an x, == can never report true, since any comparison touching an x evaluates to x โ€” the fix is ===. The third is forgetting a literal's width and base entirely, writing a = 5 instead of a = 4'd5; this frequently still simulates correctly by accident, and is exactly the kind of accident that lesson 4 covers under width and sign-extension pitfalls.

Interview questions

What is the difference between a wire and a reg in Verilog? A wire models a physical connection with no value of its own, continuously driven by something else (a gate, an assign, or a module output). A reg models a variable assigned inside a procedural block (initial/always) that holds its value until explicitly reassigned. Despite the name, declaring something reg does not by itself make it synthesize to a hardware register โ€” that depends on how it is assigned, not on the keyword.

What are the four values a Verilog signal can hold, and what does each mean? 0 and 1 are the two logic levels; x means unknown (an uninitialized register, or a real electrical conflict); z means high impedance (nothing driving the signal at all, as on an undriven wire or a tri-stated bus).

What is the difference between == and ===? == is logical equality: if either side has any x or z bit, the result is x, which an if treats as false. === is case equality: it compares every bit literally, including x and z, and always returns a definite true or false. === has no synthesis meaning and is used only in testbenches.

Key takeaways

  • A wire is a continuously driven connection with no value of its own; a reg is a variable assigned inside a procedural block โ€” the keyword does not by itself determine whether synthesis produces a flip-flop or plain combinational logic.
  • Every signal carries one of four values: 0, 1, x (unknown), or z (high impedance) โ€” an undriven wire reads z, an unassigned register reads x, and both are ordinary, defined outcomes, not errors.
  • A literal states its own width and base (8'hA5); always write both explicitly rather than relying on a bare number.
  • Bit selects (sig[3]) and part selects (sig[7:4]) must use the same high-to-low bit ordering the vector was declared with.
  • == (logical equality) returns x through any unknown bit; === (case equality) compares bits literally and always returns a definite answer โ€” only ===/!== can check for x or z directly, and only in simulation.