SystemVerilog logic vs wire vs reg (and bit): When to Use Each
In short: Use logic for almost every signal in SystemVerilog. It holds 4 states (0, 1, X, Z) and takes either one assign or procedural writes. reg is just the old name for the same thing. Use wire only when a signal has more than one driver, like a shared bus or an inout port. Use bit (2-state, only 0 and 1) for testbench counters and data where X can’t happen, never for sampling DUT outputs.
Every new SystemVerilog engineer asks the same question.
“Should this be wire, reg, or logic?”
The short answer is: logic, almost always.
The longer answer is about two separate questions:
- How many states can it hold? 4 (0, 1, X, Z) or 2 (0, 1).
- How many drivers can it have? One, or many.
Once you see those two questions, the four types sort themselves out.
Four everyday pictures
- **
wire= a water pipe.** It holds nothing by itself. It shows whatever is flowing in. No tap open? Nothing flows (Z). Two taps pushing different things? A mess (X). - **
logic= a whiteboard with one marker.** It keeps what was written on it. Only one person holds the marker. - **
reg= the same whiteboard, with an old label on it.** In SystemVerilog,regandlogicare the same type. - **
bit= a light switch.** Only on or off. It has no way to say “I don’t know”.
The four types in code
module tb;
wire w; // net: shows whatever drives it
logic l; // 4-state variable: 0, 1, X, Z
reg r; // same as logic (old Verilog name)
bit b; // 2-state variable: only 0 or 1
assign w = 1'b1; // continuous assignment
assign l = 1'b0; // logic can take an assign too (one driver)
initial begin
r = 1'bx; // procedural assignment
b = 1'bx; // X does not fit in 2 states: becomes 0
#1 $display("w=%b l=%b r=%b b=%b", w, l, r, b);
b = 1'bz;
#1 $display("bit after writing z: %b", b);
end
endmodule
Output (tested on Icarus Verilog 12.0, 6 Nov 2026):
w=1 l=0 r=x b=0
bit after writing z: 0
What to notice:
lis a variable, yet it took anassign. In plain Verilog, that needed awire.rkept the X.regis a 4-state variable, same aslogic.bturned X into 0, and Z into 0. A 2-state type has no slot for “unknown”.
Why Icarus and not Verilator here? Verilator is a 2-state simulator, so it can’t show X or Z at all. For this page I needed a simulator that keeps all four states.
4-state vs 2-state, side by side
| 4-state | 2-state | |
|---|---|---|
| Values | 0, 1, X, Z | 0, 1 |
| Types | logic, reg, wire, integer | bit, byte, int, shortint, longint |
| Starts as | X (variables), Z (undriven nets) | 0 |
| X or Z written in | Kept | Becomes 0 |
| Use for | RTL, anything that touches the DUT | Testbench counters, loop variables, random data |
logic vs wire vs reg vs bit
logic | reg | wire | bit | |
|---|---|---|---|---|
| Kind | Variable | Variable | Net | Variable |
| States | 4 | 4 | 4 | 2 |
| Holds a value | Yes | Yes | No, shows its drivers | Yes |
Written by assign | Yes, one only | Yes, one only (in SystemVerilog; not in old Verilog) | Yes, many | Yes, one only |
Written in always / initial | Yes | Yes | No | Yes |
| More than one driver | No | No | Yes, values are resolved | No |
| Picture | Whiteboard | Same whiteboard, old label | Water pipe | Light switch |
| Use it for | Almost every RTL and TB signal | Old code only | Shared buses, inout ports | TB counters and data |
The one rule worth remembering: a variable gets one continuous driver; a net can get many (IEEE 1800-2017, section 6.5).
Still not sure? Answer three questions:

When you really need wire: a shared data line
Here two devices share one line. Each drives it only when its enable is on. The rest of the time it lets go (Z).
// Two devices share one data line. Each drives it only when enabled.
module tb;
wire data; // a net: can have many drivers
logic en_a, en_b;
logic val_a, val_b;
assign data = en_a ? val_a : 1'bz; // device A
assign data = en_b ? val_b : 1'bz; // device B
initial begin
en_a = 0; en_b = 0; val_a = 1; val_b = 0;
#1 $display("nobody drives : data=%b", data);
en_a = 1;
#1 $display("only A drives 1 : data=%b", data);
en_b = 1;
#1 $display("A drives 1, B 0 : data=%b", data);
val_b = 1;
#1 $display("both drive 1 : data=%b", data);
end
endmodule
Output (Icarus Verilog 12.0):
nobody drives : data=z
only A drives 1 : data=1
A drives 1, B 0 : data=x
both drive 1 : data=1
That’s the water pipe:
- No tap open: Z.
- One tap: you see its value.
- Two taps fighting: X. That X is a bus contention bug, and you want to see it.
- Two taps agreeing: no conflict.
Change wire data to logic data and it won’t even compile. Icarus says:
ex3_two_drivers.sv:5: error: Unresolved net/uwire data cannot have multiple drivers.
A working example: the register that was never reset
This is a classic reason to keep bit out of monitors.
A register block has two registers. Both should read 0 after reset. But the designer forgot to reset STATUS. So STATUS reads X.
The monitor samples rdata into two fields: one bit, one logic.

// A register block with a bug: STATUS (addr 1) was never reset.
module regs (input logic clk, rst_n, input logic [1:0] addr, output logic [7:0] rdata);
logic [7:0] ctrl, status;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) ctrl <= 8'h00; // bug: status is missing here
assign rdata = (addr == 0) ? ctrl : status;
endmodule
module tb;
logic clk = 0, rst_n = 0;
logic [1:0] addr;
logic [7:0] rdata;
regs dut (.*);
always #5 clk = ~clk;
bit [7:0] seen_2state; // monitor field declared as bit
logic [7:0] seen_4state; // monitor field declared as logic
initial begin
#12 rst_n = 1;
for (int a = 0; a < 2; a++) begin
addr = a;
@(posedge clk);
seen_2state = rdata;
seen_4state = rdata;
// expected reset value of both registers is 8'h00
$display("addr %0d: bit sees %h -> %s | logic sees %h -> %s", a,
seen_2state, (seen_2state == 8'h00) ? "PASS" : "FAIL",
seen_4state, ($isunknown(seen_4state) || seen_4state !== 8'h00) ? "FAIL" : "PASS");
end
$finish;
end
endmodule
Output (Icarus Verilog 12.0):
addr 0: bit sees 00 -> PASS | logic sees 00 -> PASS
addr 1: bit sees 00 -> PASS | logic sees xx -> FAIL
Same DUT, same read, two answers:
- The
bitfield turned X into 00. The reset check passed. The bug walks into silicon. - The
logicfield kept the X, and$isunknown()caught it.
The light switch can’t say “I don’t know”, so it says “off”. And “off” happened to be the expected value.
In a UVM monitor, the same rule applies: any field that copies a DUT signal should be logic. Keep bit for things the testbench makes up itself.
Common mistakes (and the fix for each)
These come from real Verification Academy threads. Each one is retold in my own words with a link to the original.
1. Two assign statements on one logic
- What happened: an engineer drove one
logicsignal from twoassignlines, each with its own condition, and got “assigned by multiple continuous statements” (Verification Academy). - Why: a variable gets one continuous driver. Two is an error, even if you think only one is “active” at a time.
- Fix: merge the conditions into one
assign. If the line really is shared, like the bus above, make it awire.
2. Mixing assign and always on the same signal
- What happened: someone asked for a code example of “cannot be driven by multiple structural drivers”. An
initialblock plus anassignon the samelogicgave “written by continuous and procedural assignments” (Verification Academy). - Why: pick one style per variable. Many procedural writes are legal, but then the last write wins, and two blocks writing at the same time is a race.
- Fix: one driver per signal. If you need procedural writes, put them all in one block.
3. “Fixing” an X by driving the DUT output from the testbench
- What happened: in 2014 an engineer wanted a DUT output that showed X to read as 0. They added
assign out = (out === 1'bx) ? 1'b0 : out;in the testbench, and the X got worse (Verification Academy, 2014). - Why: that
assignis a second driver on the same net. The DUT drives X, the testbench drives 0, and the wire resolves them to X. Two taps fighting. - Fix: never drive a DUT output. Read it, and handle X in the checker with
===or$isunknown().
4. Using a logic variable for a bidirectional line
- What happened: in 2023 someone asked why
logiccan’t be used forinoutports (Verification Academy, 2023). - Why: an
inoutis driven from both sides, so it needs many drivers. Only nets allow that. - Fix: use
wireforinoutports and shared buses. In an interface, declare the shared line aswireand drive it from each side withassign ... ? value : 'z.
5. Expecting X to stay in its own bits during math
- What happened: in 2022 an engineer added two values with X in the top bits and expected the low bits to stay 0. The whole sum came out X (Verification Academy, 2022).
- Why: arithmetic is pessimistic. One X bit anywhere makes the whole result X. Bitwise operators keep X where it is.
module tb;
logic [3:0] a = 4'bxx00, b = 4'bxx00;
logic [4:0] c;
logic [3:0] m;
initial begin
c = a + b; // arithmetic: one X bit spoils the whole result
m = a & 4'b0011; // bitwise: X only where it really matters
#1 $display("a + b = %b", c);
$display("a & 4'b0011 = %b", m);
end
endmodule
Output (Icarus Verilog 12.0):
a + b = xxxxx
a & 4'b0011 = 0000
- Fix: if you see an all-X sum, look for one X input bit. Don’t trust a 2-state type to hide it for you; that only turns the bug into a wrong number.
Frequently asked questions
Is reg the same as logic in SystemVerilog?
Yes. They are the same 4-state type. logic is the clearer name, because a reg was never always a register.
Can logic replace wire everywhere?
No. Anything with more than one driver needs a net: shared buses, tri-state lines, inout ports.
When should I use bit instead of logic?
- For values the testbench creates itself: loop counters, random data, IDs.
- Never for anything copied from the DUT. A
bitturns X into 0 and hides the bug.
What does X mean in simulation?
Unknown. A register never reset, two drivers fighting, or math on an unknown input. In silicon it would be a real 0 or 1, you just can’t tell which.
Does logic make simulation slower?
A 4-state value takes more memory than a 2-state one, which is why big testbench arrays often use bit or int. For DUT signals, the X checking is worth it.
What to read next
- SystemVerilog arrays and queues: fixed, dynamic, associative and queue
- SystemVerilog interview questions: logic vs wire is a favorite
References
- IEEE 1800-2017, SystemVerilog standard: section 6.5 (nets and variables), 6.6 (net types), 6.11 (integer data types, 2-state and 4-state).
- Verification Academy forum threads linked in the mistakes section (accessed 6 Nov 2026).
- All code tested on Icarus Verilog 12.0 on 6 Nov 2026.
Part of the learning path SystemVerilog for verification
