When every driver releases a net, simulation reads z. In silicon the wire is a small capacitor that drifts with leakage and noise.
A receiver sitting at an intermediate voltage can draw excess current or flip randomly. A shared bus therefore needs a resting value:
Fix | How | Trade-off |
|---|---|---|
Pull-up / pull-down resistor | Weakly drives the net to 1 or 0 when nobody else does | Simple; wastes current when another driver pulls the other way; slow edge. |
Bus keeper (bus holder) | A weak latch that remembers the last driven value | Almost no settled static current, apart from leakage; holds the last value rather than a fixed one, so it does not guarantee a startup state, and a driver must be strong enough to override it (briefly drawing override current). |
Always drive it | Add a default driver, or use a mux (see next lesson) | Cleanest; costs a little logic. |
Verilog has net types that model the pull behaviour: tri0 resolves to 0 and tri1 to 1 when undriven.
Watch w_tri, w_tri1 and w_tri0. On a phone, swipe the diagram sideways.
Slot 1: released (en is 0, d is ignored): w_tri reads z, w_tri1 reads 1, w_tri0 reads 0.
Slot 2: driven 1 (en is 1, d is 1): all three read 1.
Slot 3: driven 0 (d is 0): all three read 0.
Slot 4: released again: w_tri reads z, w_tri1 reads 1, w_tri0 reads 0.
Slot 5: still released, so the resting values hold.
wand (wired-AND) and wor (wired-OR) resolve multiple active drivers with an AND or OR instead of producing x. They model open-drain or open-collector buses, where any device can pull the line low.
Net type | Undriven | Drivers 1 and 0 together | Models |
|---|---|---|---|
|
|
| Ordinary net |
| 0 |
| Pull-down |
| 1 |
| Pull-up |
|
| 0 | Open-drain / wired-AND |
|
| 1 | Wired-OR |
Open-drain buses such as I²C use a pull-up resistor plus devices that only pull low. Any device pulling low wins, which is a wired-AND of the devices' release signals.
Read the circuit as switches: a device whose pull-low signal is 1 shorts the line to ground; a device at 0 is an open switch.
Both released (first table row): no path to ground, so the resistor pulls the line to 1.
Either pulls low: the closed switch wins over the weak resistor, so the line reads 0.
No clash: two devices pulling low together still give 0, so open-drain lines cannot short a 1 against a 0.
In RTL model the line as tri1, and let each device drive either 0 or z, never a hard 1: with both devices released, the pull-up supplies the 1. A bare wand is different. With every driver released it reads z, not 1, so a wand open-drain line also needs a pull-up (for example a pullup primitive on that net). Declaring the net in simulation does not install a pull-up in the real system.
Because devices can only pull the line low or release it. To send a 1 a device releases the line (drives z), and the pull-up brings it high. Without the pull-up, a released line would float.
The testbench below checks the open-drain contract directly with two devices that each drive 0 or z: both released, either one low, and both low.
module tb_open_drain;
tri1 bus; // the pull-up supplies the released level
wand wbare; // no pull-up on this one
logic a_low, b_low; // 1 = device pulls the line low, 0 = releases it
assign bus = a_low ? 1'b0 : 1'bz;
assign bus = b_low ? 1'b0 : 1'bz;
assign wbare = a_low ? 1'b0 : 1'bz;
assign wbare = b_low ? 1'b0 : 1'bz;
int errors = 0;
initial begin
for (int i = 0; i < 4; i++) begin
a_low = i[1];
b_low = i[0];
#1;
if (bus !== ((a_low || b_low) ? 1'b0 : 1'b1)) errors++; // released -> pulled to 1
if ((a_low || b_low) && wbare !== 1'b0) errors++; // any low device wins
if (!a_low && !b_low && wbare !== 1'bz) errors++; // bare wand: released is z
end
if (errors == 0) $display("PASS: open-drain bus is 1 only when both release; a bare wand needs its own pull-up");
else $display("FAIL: errors=%0d", errors);
$finish;
end
endmoduleDo | Avoid |
|---|---|
Give every shared net a resting value ( | Leaving a CMOS input floating, where it can sit mid-rail and draw current |
Drive an open-drain line with 0 or | Driving a hard 1 onto a shared line that others can pull low |
tri net. Predict what a receiver reads in simulation.z. Nobody drives the net. In silicon the voltage floats, so give it a pull-up, pull-down, keeper, or default driver.
tri1 net. Predict its value.1. tri1 resolves to 1 when undriven, and tri0 resolves to 0. With drivers 1 and 0 together, both still give x.
This testbench checks pulls, wired logic, and the plain-net clash.
module tb_nets;
timeunit 1ns; timeprecision 1ps;
tri0 pd; // undriven -> 0
tri1 pu; // undriven -> 1
wand w; assign w = 1'b1; assign w = 1'b0; // two drivers: 1 AND 0
wor o; assign o = 1'b1; assign o = 1'b0; // two drivers: 1 OR 0
tri plain; assign plain = 1'b1; assign plain = 1'b0; // plain net: conflict
int errors = 0;
initial begin
#1;
if (pd !== 1'b0) errors++; // pulled down when nobody drives
if (pu !== 1'b1) errors++; // pulled up when nobody drives
if (w !== 1'b0) errors++; // wired-AND of 1 and 0
if (o !== 1'b1) errors++; // wired-OR of 1 and 0
if (plain !== 1'bx) errors++; // normal net: contention
if (errors != 0) $fatal(1, "FAIL: tb_nets errors=%0d (pd=%b pu=%b w=%b o=%b plain=%b)", errors, pd, pu, w, o, plain);
$display("PASS: tri0/tri1/wand/wor behave as described");
$finish;
end
endmodule