Two-State vs Four-State Logic
In software, a bit is 0 or 1. In Verilog, a bit can be one of four values:
0- the wire is driven low.1- the wire is driven high.x- the wire's value is unknown. The simulator can't tell.z- the wire is high-impedance. Nothing is driving it.
That four-state model exists because hardware has the same problem. A real wire can be tied low, tied high, undefined (driven by two sources fighting each other), or floating (no driver at all). The simulator has to model all four to be useful.
How x Shows Up
Run this and look at the output:
a is declared but never written, so it sits at x. Adding x + 5 produces x - any arithmetic with an unknown produces an unknown. The output reads aaaa worth of x's.
Common sources of x in your designs:
- A
regdeclared but never reset (most synthesizable Verilog uses an explicit reset to clear them). - A
casestatement with nodefault, hit by an input value none of the cases matched. - A
wirethat lost its only driver after a refactor. - An
if/elsechain where one branch doesn't assign a signal that the other does (latchesxif uncovered). - Reading past the end of a vector or array.
X Propagation: A Tiny Bit of x Ruins Everything
The cruel thing about x is that it propagates. One x bit in an operand turns the whole result x:
Notice that 0 & x is 0 (AND with 0 is always 0) and 1 | x is 1 (OR with 1 is always 1). The simulator is bit-by-bit pessimistic but still respects identities. Arithmetic and comparison aren't so generous.
This is why a single uninitialized register can make an entire output bus go xxxx. Trace backwards from any x and you'll find the source.
How z Shows Up
z is the value of a wire that nobody is driving:
Two patterns in that snippet:
floatingis just declared and never driven. It defaults toz.data_outis a deliberate tri-state. Whenenableis low, the output explicitly releases toz. That's how a bus driver "lets go" so another driver can take over.
On internal logic, z is almost always wrong. On a bidirectional pin or a shared bus, z is exactly right.
Comparing With == vs ===
The regular equality operator == returns x when either operand has an x or z bit:
=== (and its partner !==) does a strict bit-by-bit comparison including x and z. Use it whenever you need to test for or against x/z in a testbench. === isn't synthesizable, but inside an initial block in a testbench that doesn't matter.
The $isunknown(expr) system function is the cleanest way to ask "does this expression have any x or z bits?" - returns 1 if yes, 0 if no.
Using x as an Intentional Don't-Care
A controversial but legitimate pattern: 'x in a default case of a state machine tells the synthesizer "this state is unreachable, optimize freely":
case (state)
IDLE: next_state = go ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = 'x; // unreachable
endcase
The synthesizer can use the x to merge states and reduce gate count. In simulation, if your reasoning was wrong and the default is reached, you'll see x propagate from next_state and the bug becomes visible immediately.
Use this only when you've thought through whether the default really is unreachable. If you haven't, set the default to a safe state instead.
Common Debugging Recipe
You're staring at a waveform full of x. The recipe:
- Find the earliest
x. Walk the waveform back in time. The earliest signal to goxis the closest to the source. - Find its driver. Open the source. What assigns this signal? An
assign? Analwaysblock? - Check the inputs to the driver. If the driver's RHS has any
x, propagation is doing what it's supposed to - the bug is upstream. - If the driver has clean inputs but produces
x, the driver is incomplete. Acasemissing a default, anifmissing anelse, a register missing a reset.
Most x-storm bugs collapse to one of: missing reset, missing default, or a sub-module that isn't connected.
What Comes Next
You now have the complete data-type story: wire/reg, vectors, parameters, number literals, and the four-state logic model. The next chapter starts using all of it to build expressions - operators of every flavor, including the bit-level operators that wouldn't make sense in software.
Frequently Asked Questions
What does x mean in Verilog?
x is the unknown value. A signal that's x could be either 0 or 1 - the simulator can't tell. It shows up when a signal hasn't been driven, when two drivers contend, when a register is read before reset, or anywhere undefined behavior would otherwise propagate silently. Treat x as a bug signal; it almost never means what you want.
What does z mean in Verilog?
z is the high-impedance value - the wire isn't being driven at all. It's the legitimate state of tri-state outputs (data buses, bidirectional pins), but on internal signals z is usually a mistake meaning 'nothing is connected here'. Synthesizers reject most z patterns outside of explicit tri-state output enables.
Why is my Verilog output xxxx?
Almost always because a signal isn't being driven by anything, or because an operation propagated x from another signal. Walk backwards: which signal is x, what feeds it, is the driver active? Common culprits are missing default cases in case statements, registers that aren't reset, and wires that lost their driver after a refactor.
How do you check for x or z in Verilog?
Use the === operator, which compares bit-exactly including x and z. a === 1'bx is true when a is actually x. The regular == returns x when either operand has an x bit, which means a == 1'bx is never the answer you want. There's also $isunknown(a), which is a tidy boolean.
Can you assign x as a default in Verilog?
Yes, and it's a deliberate technique in case statements: default: out = 'x; tells the synthesizer 'I promise this case never happens, optimize freely'. The cost is that if it does happen in simulation, x propagates and you see the bug. Use it when you've already proven the default is unreachable, not as a way to avoid writing the case.