12.7 Tri-State Logic: The Third State, Z
Explain the three states of a tri-state output (0, 1, Z), why Z is not the same as logic 0, and the rules for safely sharing a bus among multiple tri-state drivers.
Most logic outputs only ever actively drive two states — LOW or HIGH — but a tri-state (three-state) output adds a third: Z, high impedance, in which the output is electrically disconnected from the circuit rather than actively driven to either voltage. Tri-state logic is the classic technique that lets multiple devices share a single wire (a bus) without permanently wiring their outputs together, and it underlies memory buses, bidirectional microcontroller I/O pins, and any hardware interface where several devices must be able to 'speak' on the same physical line at different times.
Core concept: an enable-controlled buffer with three outputs
A tri-state buffer has a data input, a data output, and an enable control. When enabled, it behaves like an ordinary buffer, actively driving the output to whatever logic level the data input specifies (0 or 1). When disabled, its output stage electrically detaches — both its pull-up and pull-down transistors turn off — leaving the output pin in a high-impedance state, Z, where the line's actual voltage is determined by whatever else is connected to it (another active driver, a pull-up/pull-down resistor, or simply parasitic capacitance if nothing else is present).
The spec's example, assign bus = enable ? data : 1'bz;, captures exactly this behavior in HDL: when enable is true, the bus wire is actively driven to data; when enable is false, the assignment drives 1'bz — hardware Z, not 0 — meaning this particular driver stops participating on the bus entirely rather than pulling it LOW.
Why Z is not the same as logic 0
This is the spec's own tricky point and it is worth internalizing precisely: logic 0 is an *actively driven* state — a low-impedance path to ground, capable of sinking current and forcing the line LOW regardless of what else is connected. Z is the *absence* of active driving — a high-impedance state that doesn't force the line to any voltage at all. A voltmeter reading '0V' on a Z line could simply mean nothing is currently holding it anywhere, and its actual voltage might drift, be determined entirely by a different active driver elsewhere on the bus, or float unpredictably if no pull resistor or other driver is present.
This distinction matters enormously in simulation and debugging: a signal stuck reading 'Z' in a waveform viewer indicates no driver is currently active on that net (often a sign of a disabled tri-state buffer, a missing pull-up, or a bus-contention bug), which is a fundamentally different diagnosis than a signal stuck reading '0'.
Sharing a bus: rules and hazards
The entire point of tri-state outputs is to let multiple drivers share one physical wire safely, but only if exactly one driver is enabled at any given instant. If two tri-state outputs are enabled simultaneously and try to drive the line to different logic levels, the result is bus contention — a direct low-impedance path fights between the two active outputs, which can draw excessive current, corrupt the logic level seen by every receiver on the bus, generate heat, and in sustained cases damage the driving devices. Correct multi-driver bus design therefore requires enable logic (often decoded from an address or a select signal) that guarantees mutual exclusivity — never more than one enable active at once.
The opposite failure is equally real: if *no* driver is enabled, the bus is left entirely in Z, and its voltage becomes undefined, drifting with leakage and parasitic capacitance and potentially settling near a threshold voltage that causes downstream CMOS receivers to draw excess shoot-through current (the same danger covered in the CMOS lesson for floating inputs). This is why undriven tri-state buses are almost always given a weak pull-up or pull-down resistor, or a 'bus-keeper' circuit (a small feedback latch that holds the last driven value once all drivers release the bus) to guarantee a defined idle state.
Tri-state states and HDL syntax
1'bz is a literal high-impedance bit — distinct from 1'b0. Simulators track Z (and X, unknown) as first-class logic values alongside 0 and 1.
States of a tri-state output:
0 — actively driven LOW
1 — actively driven HIGH
Z — high impedance (not driven; electrically disconnected)
assign bus = enable ? data : 1'bz;Code Examples
Only two of the four enable combinations are safe. The design must guarantee, structurally (e.g., through a one-hot decoder), that en_a and en_b are never simultaneously 1 — software-level 'promises' aren't sufficient in real hardware, where a brief overlap during a control-signal transition can still cause a momentary contention glitch.
Exactly one enable active at a time avoids contention; zero enables active leaves the bus undefined without a pull resistor.
en_a, en_b are guaranteed mutually exclusive (never both 1):
bus_a = en_a ? data_a : Z
bus_b = en_b ? data_b : Z
bus = bus_a (wired together with bus_b on the same net)
en_a=1, en_b=0 → bus driven by data_a, driver B is Z → OK
en_a=0, en_b=1 → bus driven by data_b, driver A is Z → OK
en_a=1, en_b=1 → BOTH drive the bus → CONTENTION if data_a != data_b
en_a=0, en_b=0 → bus fully Z → undefined unless a pull resistor/bus-keeper existsBus contention vs correct tri-state sharing
A correctly-arbitrated bus never has two active drivers overlap in time; contention shows up as a fought, indeterminate region.
Correct (mutually exclusive):
en_a: __/‾‾‾‾\________
en_b: ________/‾‾‾‾\__
bus: --A-----B------ (only one driver active at a time)
Contention (both enabled, disagreeing data):
en_a: __/‾‾‾‾‾‾‾‾‾‾\__
en_b: ______/‾‾‾‾‾‾\____
bus: --A---XXXX---A-- (XXXX = both drivers fighting the line)Important Rules
Z means 'not actively driven,' not 'driven to 0' — never treat them as equivalent in analysis or debugging.
Exactly one tri-state driver must be enabled at any instant on a shared bus — never zero (undefined) and never more than one (contention).
Undriven tri-state buses need a pull-up/pull-down resistor or bus-keeper to avoid an indeterminate floating state.
Enable-signal timing must itself avoid overlap — a brief glitch enabling two drivers simultaneously during a transition can still cause momentary contention.
Shared parallel buses and bidirectional pins
Classic parallel memory and peripheral buses (address/data buses on older microprocessor systems) rely entirely on tri-state outputs so that the CPU, RAM, and various peripheral chips can all connect to the same physical data lines without permanent wiring conflicts, with only the currently-addressed device's tri-state buffers enabled. The same mechanism underlies bidirectional GPIO pins on microcontrollers, where a single physical pin can act as either an output (driver enabled) or an input (driver disabled, Z, letting an external signal or pull resistor set the level).
Assuming a disabled tri-state output actively pulls its line LOW, and treating a 'Z' reading the same as a '0' reading in a waveform.
Recognize Z as a high-impedance, non-driving state whose actual voltage depends on other drivers or pull resistors, distinct from an actively-driven 0.
Treating Z as 0 can mask real bugs — a floating bus reading Z in simulation might look harmless if misread as '0,' but in real hardware that same net could drift to an indeterminate voltage and cause downstream shoot-through current or misreads, which is a fundamentally different and more serious problem.
Tricky problem (from the spec): Z is not the same as logic 0. What is the key electrical distinction? A. Z means the voltage is always exactly 0V, just measured differently B. 0 is an actively-driven, low-impedance state that forces the line LOW; Z is a high-impedance, non-driven state whose actual voltage is determined by whatever else is connected to the net C. Z only exists in simulation and has no real hardware meaning D. Z and 0 are interchangeable as long as the enable signal is also 0
B. 0 is an actively-driven, low-impedance state that forces the line LOW; Z is a high-impedance, non-driven state whose actual voltage is determined by whatever else is connected to the net — A '0' output actively sinks current and forces the node LOW through a low-impedance path. A 'Z' output detaches entirely — no active drive in either direction — so the node's real voltage depends on other drivers, pull resistors, or leakage, which can be anywhere in the voltage range, not necessarily near 0V.
Interview question: what electrical event occurs if two tri-state outputs on the same bus are both enabled at once and try to drive opposite logic levels? A. The bus safely averages to a valid mid-level logic signal B. Bus contention — a low-impedance path forms directly between the two active outputs, which can draw excessive current, corrupt the voltage seen by receivers, and potentially damage the driving devices C. Nothing happens; tri-state buffers automatically arbitrate which one wins D. The bus is forced into Z automatically by the shared wire
B. Bus contention — a low-impedance path forms directly between the two active outputs, which can draw excessive current, corrupt the voltage seen by receivers, and potentially damage the driving devices — Tri-state buffers provide no automatic arbitration — if two are simultaneously enabled and disagree on the driven level, one output's pull-up fights the other's pull-down (or vice versa) directly through the shared wire, creating a genuine low-impedance short-like current path that is a real electrical hazard, not just a logic-level ambiguity.
Design the enable logic for a 4-device tri-state bus so that exactly one device drives the bus whenever a valid 2-bit select code is presented, and the bus is safely defined even when the select code is invalid/reset. Describe your approach.
Decode the 2-bit select signal through a 2-to-4 decoder to produce four one-hot enable lines (en0..en3), guaranteeing at most one is ever asserted for any valid select code — this structurally prevents contention since a decoder's outputs are mutually exclusive by construction. For the undefined/reset case, add a weak pull-up (or bus-keeper latch) on the shared bus line so that when all four enables are deasserted (e.g., during reset, before any valid select code is driven), the bus settles to a known, defined logic level rather than floating indeterminately.
Think about decoding the 2-bit select into four mutually-exclusive one-hot enables, plus what should happen to the bus during reset or an out-of-range code.