A net (wire, tri, wand, wor) holds no value of its own. Its value is resolved continuously from everything driving it, which models real electrical nodes where several devices can connect.
A variable (logic, bit, int, historically reg) stores the last value assigned to it. There is no resolution function, so IEEE 1800 allows a variable only one continuous driver and makes a second one an error. Not every simulator reports it, so lint for it too.
Drivers on a plain wire | Resolves to |
|---|---|
0 against 1 | X, the conflict stays visible |
0 against Z | 0, the driven value wins |
1 against Z | 1, the driven value wins |
Nothing drives | Z, the net floats |
| 0 wins / 1 wins instead of X |
// snippet: two hard drivers fighting on one net read back X
wire w;
assign w = 1'b1;
assign w = 1'b0; // w resolves to 1'bxIn Verilog-2001 the choice of reg versus wire described how a signal was assigned. SystemVerilog logic erases that bookkeeping: one always block, one continuous assign, or one module output, and single-driver checking catches wiring bugs at compile time.
The only place logic cannot go is a true multiply-driven net: tri-state buses, bidirectional pads, and wired-AND/OR buses still need wire or tri because they need resolution. A second continuous driver on a variable, or a second writer to one always_comb, always_ff, or always_latch variable, is illegal under IEEE 1800.
Two plain always blocks writing one variable is legal but racy, so it stays a code-review catch rather than a compile error. Ports add one subtlety: plain input logic is a net underneath, and spelling input var logic forces true variable semantics with single-driver checking.
// bad-example: a second continuous driver on a variable is illegal
module two_drivers;
logic a;
assign a = 1'b1;
assign a = 1'b0; // illegal: variables have no resolution function
endmodule// snippet: plain input ports are nets underneath; var forces a variable
module m (
input logic [7:0] plain,
input var logic [7:0] strict
);
endmoduleA tri-state bus is the canonical multiply-driven net. Each source drives its value or Z, and per-bit resolution picks the live driver, which no variable type can express.
Z. Every driver floats, and a net with nothing driving it resolves to high impedance on every bit.
Modern RTL style is logic everywhere except true buses. Internal signals and outputs gain single-driver checking, while the shared bus stays a net.
// Modern RTL style: logic everywhere except the shared bus
module alu (
input logic [7:0] a, b,
input logic [1:0] op,
output logic [7:0] y
);
logic [7:0] sum;
always_comb begin
sum = a + b;
unique case (op)
2'b00: y = sum;
2'b01: y = a & b;
2'b10: y = a | b;
2'b11: y = a ^ b;
endcase
end
endmoduleSingle-driver values plus a tri-state bus tracking the live driver through an enable swap and a data change.
module tb_drivers;
timeunit 1ns; timeprecision 1ps;
wire w2;
assign w2 = 1'b1; // one driver: a plain known value
wire [7:0] bus;
logic en_a = 1'b1, en_b = 1'b0;
logic [7:0] a_data = 8'hA5, b_data = 8'h3C;
assign bus = en_a ? a_data : 8'bz;
assign bus = en_b ? b_data : 8'bz;
int errors = 0;
initial begin
#1;
if (w2 !== 1'b1) errors++;
if (bus !== 8'hA5) errors++; // Z loses, the driven value wins
en_a = 1'b0; en_b = 1'b1; #1;
if (bus !== 8'h3C) errors++;
a_data = 8'hFF; en_a = 1'b1; en_b = 1'b0; #1;
if (bus !== 8'hFF) errors++; // bus follows the live driver
if (errors != 0) $fatal(1, "FAIL: driver resolution saw %0d unexpected results", errors);
$display("PASS: single driver read clean and tri-state tracked the live driver");
$finish;
end
endmodulePASS: single driver read clean and tri-state tracked the live driverwire with 1 and 0, and the same pair drives one logic. What happens in each case?The wire resolves to X at runtime. The logic is illegal, because a variable has no resolution function for two drivers. Not every simulator stops on it, so lint for it.
bus is a wire, a net, so it may have many drivers and resolves them. The single-driver rule is for variables such as logic.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 (-g2012) runs them too, with the same output.
The illegal two-driver listing is not run. Icarus 13 rejects it at elaboration; Verilator 5.048 accepts it without an error, so a lint step is the reliable check.