Digital Design / Module 3: Verilog that means hardware
13. wire, reg, logic and 4-state values
Avoid the most common confusion in Verilog.
See it move
The idea
Simulation uses four values, not two. 0 and 1 are the usual logic levels. x means the simulator doesn't know the value, and z means nothing is driving the wire.
wire, reg and logic
The names are misleading. reg does not mean a register. It only means the signal is assigned inside a procedural block.
reg y;
always @(*) y = a & b; // y is a reg, yet this is just an AND gate
wire z;
assign z = a & b; // the same hardwareSystemVerilog's logic replaces both in most code. It can be assigned from either kind of construct, and tools complain if it has more than one driver. That catches real bugs. Use wire when a net truly has multiple drivers, such as a tri-state bus.
How x behaves
An unknown spreads, unless a known input decides the output anyway. 0 & x is 0, because an AND with a 0 input must be 0. 1 & x is x. This is useful: a reset you forgot shows up as x across your waveform.
Vectors and signedness
wire [7:0] a is eight bits with bit 7 as the MSB. Vectors are unsigned unless you declare them signed. If any operand in an expression is unsigned, the whole expression is treated as unsigned, which can silently break a comparison.
Common mistake
Trusting x to tell you everything. The expression if (x_signal) treats x as false and takes the else branch, so an unknown can disappear in simulation while real silicon does something else. This is called X-optimism. Reset everything that needs it, and check with === in testbenches where you must detect x.
Key ideas
- 'reg' does not guarantee a register.
- SystemVerilog 'logic' replaces most uses, with a single driver.
- X means unknown and Z means undriven.
Your exercise
Find the bug
This code has one deliberate bug. Read the symptom, then click the line you think is wrong.
The fix
module lt8(input signed [7:0] a, b, output lt);
Why it failed
Vectors are unsigned unless declared otherwise, so -1 is read as 255, which isn't less than 1. Declaring the ports signed makes the comparison treat the top bit as a sign. There is no error or warning from the language, only a wrong answer.
Interview questions
coreDoes reg always synthesize to a flip-flop?
No, always @(*) with a reg produces combinational logic.
coreWhat is 0 AND X?
0.
basicWhat is the difference between x and z in Verilog simulation?
z means nothing is driving the wire; it is floating. x means the simulator cannot decide the value, for example because the wire has conflicting drivers or a register was never initialised. A z feeding a gate input usually becomes x at the gate's output.
basicWhat are 1 | x, 0 | x and 1 ^ x?
1 | x is 1, because an OR with a 1 input is 1 whatever the other input is. 0 | x is x, because the result now depends on the unknown. 1 ^ x is x, because XOR always depends on both inputs, so an unknown on either side makes the output unknown.
coresel is x in simulation. What does this block give for y, with a = 1 and b = 0?
always @(*) if (sel) y = a; else y = b;
y becomes 0. An if treats an x condition as false and takes the else branch, so y = b. The unknown has vanished from the waveform, even though real hardware could output either value. This is X-optimism, and it is how a missing reset can hide in simulation.
coreA testbench checks if (dout == 1'bx) to detect an unknown output. Why does that never fire, and what should it use?
With ==, any x operand makes the result x, and an if treats x as false. So the check is never taken, even when dout really is x. The case equality operator === compares x and z literally, so dout === 1'bx returns 1 when dout is x. Testbenches use === and !== for exactly this reason.
coreWhy does this fail to compile, and what are the two fixes?
wire y; always @(*) y = a & b;
In Verilog-2005, a signal assigned inside an always block must be declared as a variable such as reg, and a wire cannot be assigned there. Either declare y as reg, which still gives a plain AND gate, or keep it a wire and write assign y = a & b;. SystemVerilog's logic accepts either style.
coreTwo drivers share one wire through tri-state buffers, as shown. What does t show when only the first is enabled with v1 = 1, when both are enabled with different values, and when neither is enabled?
assign t = en1 ? v1 : 1'bz; assign t = en2 ? v2 : 1'bz;
With one driver enabled, t is 1: the other driver outputs z, and a value beats z. With both enabled driving 0 and 1, t is x, a real bus fight. With neither enabled, t is z, floating. A tri-state bus needs control logic that enables exactly one driver at a time.
advancedx is a signed 8-bit wire holding -4. Why does y hold 253 instead of -3?
wire signed [7:0] x = -8'sd4; wire signed [15:0] y = x + 1'b1;
1'b1 is an unsigned literal, and an unsigned operand makes the whole expression unsigned. So x is zero-extended to 16 bits as 0x00FC rather than sign-extended to 0xFFFC, and the sum is 0x00FD = 253. Use a signed operand such as x + 1 (an unsized integer is signed) or x + 8'sd1. Beware 1'sb1: a 1-bit signed 1 means -1.
advancedA counter has no reset. In simulation it stays xxxx forever. In silicon it counts. Which is more truthful, and what should you do?
always @(posedge clk) cnt <= cnt + 4'd1; // no reset
Simulation is right to be suspicious: the flops power up to unknown values, x + 1 is still x, and the simulator cannot know where counting starts. Silicon starts somewhere arbitrary and counts from there, which may or may not be acceptable. If the starting value matters, add a reset. If it truly doesn't, for example in a free-running datapath, the x is expected, but anything that depends on it must not use it until it is known.