Menu

Verilog X and Z Values: Unknown and High-Impedance Signals Explained

Verilog signals have four possible values, not two. Here's what x (unknown) and z (high-impedance) actually mean in simulation, and how to debug them.

This page includes runnable editors - edit, run, and see output instantly.

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 reg declared but never reset (most synthesizable Verilog uses an explicit reset to clear them).
  • A case statement with no default, hit by an input value none of the cases matched.
  • A wire that lost its only driver after a refactor.
  • An if/else chain where one branch doesn't assign a signal that the other does (latches x if 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:

  • floating is just declared and never driven. It defaults to z.
  • data_out is a deliberate tri-state. When enable is low, the output explicitly releases to z. 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:

  1. Find the earliest x. Walk the waveform back in time. The earliest signal to go x is the closest to the source.
  2. Find its driver. Open the source. What assigns this signal? An assign? An always block?
  3. 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.
  4. If the driver has clean inputs but produces x, the driver is incomplete. A case missing a default, an if missing an else, 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.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED