Several blocks each drive one shared on-chip wire when enabled. In modern ASIC design this sharing is almost always avoided, for practical reasons:
Timing analysis struggles with enable-controlled drivers and the transitions between them; static timing tools treat tri-state buses as special cases.
Scan test (DFT) needs every net to have a known value; a bus with several enables is hard to control and risks contention during test.
Contention hazards: overlapping enables for even a gate delay cause a transient short circuit, extra power and noise.
Tool and library support: many flows disallow or restrict internal z, and some technologies lack tri-state cells.
A mux is simpler: an AND-OR select is deterministic, glitch-free in steady state, and trivially analyzable.
Replace the shared tri-state net with a data mux whose select comes from the same output enables. The behaviour is identical whenever exactly one enable is active.
Watch bus_tri against bus_mux. On a phone, swipe the diagram sideways.
Slots 1 and 2: only en1 drives 1, so both read 1.
Slots 3 and 4: en2 joins with 0 while en1 still drives 1: the bits disagree, so bus_tri reads x, while bus_mux reads 1 (the OR of 1 and 0). Overlap alone is not always x: where the two words agree, the bus stays known.
Slot 5: en1 turns off, so only en2 drives 0: both read 0.
Slot 6: both off: bus_tri reads z, while bus_mux reads 0.
Situation | Tri-state bus | AND-OR mux |
|---|---|---|
Exactly one enable | That driver's data | That driver's data |
No enable |
| 0 (defined) |
Two enables | Resolved bit by bit: agreeing bits stay known, opposing bits become | OR of both words: known when the inputs are known, but potentially wrong |
Timing analysis | Special cases | Ordinary logic |
Scan test | Problematic | Straightforward |
The tri-state bus reads z because it floats. The AND-OR mux reads 0 because it is defined.
The tri-state bus risks x on every bit where the drivers disagree, and a transient short circuit. The mux gives the OR of both values, which is potentially wrong but known if the inputs are known.
Both read that shared word with no x, because every bit agrees. The design is still wrong: two owners were enabled at once, which is why the ownership assertion checks the enables, not the data.
I/O pads of the chip: bidirectional pins, off-chip buses.
Board-level shared buses and legacy memory interfaces.
Hand-instantiated pad cells from the library, not inferred in the core RTL.
Replace it with an AND-OR mux driven by the same enables (or a proper mux with an encoded select), and add an assertion that at most one enable is active at any time.
This testbench compares the tri-state bus with its mux twin, then deliberately overlaps two opposing drivers between clock edges. The event-driven assertion must catch that overlap while the clocked assertion misses it.
module bus_tristate (
input logic [7:0] d0, d1, d2,
input logic oe0, oe1, oe2,
output wire [7:0] bus
);
assign bus = oe0 ? d0 : 8'bz;
assign bus = oe1 ? d1 : 8'bz;
assign bus = oe2 ? d2 : 8'bz;
endmodule
module bus_mux (
input logic [7:0] d0, d1, d2,
input logic oe0, oe1, oe2,
output logic [7:0] bus
);
assign bus = ({8{oe0}} & d0) | ({8{oe1}} & d1) | ({8{oe2}} & d2); // AND-OR select
endmodule
The testbench counts the expected assertion violations instead of reporting them as $error; a normal monitor should report unexpected violations as errors. The #0 lets this zero-delay example settle before checking the changed enables. Use timing-aware checks on delayed pad models for physical signoff.