Module 8: RTL Coding for Quality & Synthesis ยท 8.4 Common RTL Bugs & Debugging

Multi-Driver Conflicts

The previous article covered a bug at the boundary between modules. This one is about a bug at the boundary between drivers.

A wire driven by two assign statements, or by two module output ports, is not a syntax error in Verilog. It is legal, and the simulator has a documented rule for what happens. That precision is the trap: a real driver conflict can look like a reasonable value under many input combinations and only show itself under the one where the two drivers disagree.

How simulation resolves multiple drivers

When two sources drive the same wire with different values, the simulator resolves the conflict to X, meaning “two disagreeing sources and no way to know which is right”. If both drivers agree, there is no visible conflict; the wire shows that value with nothing in the waveform hinting that two sources were driving it.

// Two continuous assignments to the same wire: legal Verilog, dangerous design
assign bus_data = (mode == 1) ? data_a : 8'bz;
assign bus_data = (mode == 2) ? data_b : 8'bz;

If mode can never be 1 and 2 at once (it cannot, for a single selector), this example resolves safely: whichever driver is not selected outputs high-impedance z, and z combined with a driven value resolves to the driven value. That is how tri-state buses are meant to work, and it is the legitimate version of multiple drivers on one wire. The dangerous case is different:

// Genuine conflict: BOTH assignments can drive real, disagreeing values
// at the same time, with nothing preventing it
assign status = error_flag ? 8'hFF : 8'h00;
assign status = busy_flag  ? 8'h01 : 8'h00;

If error_flag and busy_flag are ever both true, two sources drive real, disagreeing values (8'hFF and 8'h01) at the same instant. The simulator resolves to X, and this passes every test where the flags never overlap and fails the first time a test, or a real operating condition, makes them overlap.

Where this comes from in real RTL

Two assign statements to the same net, as above: usually a copy-paste error, or two pieces of logic each reasoned about correctly on its own and never checked against each other.

Two module instances both driving a shared output port at the top level, connected to the same wire with no tri-state or mux arbitrating between them. Common when integrating IP blocks that were each designed assuming they would be the sole driver of a bus.

A reg written by more than one always block. Modern toolchains usually catch this immediately as an error or an unambiguous warning, since it is even more directly forbidden than two assigns on a wire, but it is the sequential cousin of the same problem.

Two drivers on one wire: safe when one is always z, contention when both drive
Figure 1: When two drivers on a wire are safe and when they conflict

Finding one

Many synthesis tools and some lint tools flag multi-driver nets explicitly, a “net has multiple drivers” warning naming the signal, much like the latch warning in the previous article. Take it as seriously. The first question is the same as in the tri-state example: can these two drivers ever disagree at the same time, given every reachable combination of the conditions controlling them? If the honest answer is “not currently, but nothing in the code prevents it”, that is a latent bug worth fixing now, before a later change to one condition turns a coincidence into a conflict.

Some simulators, by default, do not flag properly tri-stated (z-based) multi-driver nets at all, since that is the legitimate case. So a broken tri-state bus, where the “off” state is not actually z for some input combination because of a mistake in the enable logic, looks identical at a glance to a working one, right up until the combination where two drivers are both active gets exercised.

A design that resolves an accidental conflict to a value that looks reasonable for the vectors run so far is more dangerous than one that fails loudly. If your simulator’s X-optimistic settings let disagreeing drivers resolve to some real value rather than a visible X, you can miss a conflict for a long time. That is an argument for occasionally running with X-pessimistic settings as a verification technique, precisely because it surfaces this class of quiet bug.

Practice Exercise

An SPI controller and a UART controller both drive a shared status_led output directly with their own assign statements, each meaning to light the LED when its peripheral is active. Explain the risk and propose a fix that removes the possibility of contention rather than making it unlikely.

Solution

If the two peripherals can be active at the same time, which nothing in the description rules out, both assign statements can drive status_led to different values at once, the contention case from this article, resolving to X and lighting the LED unpredictably or not at all.

The fix that removes the possibility: route both peripherals’ activity signals into one piece of arbitration logic, an OR if either should light the LED, or a priority structure (Section 5.3) if one should take precedence, and make that one piece of logic the sole driver of status_led, with neither peripheral driving the shared wire directly. That turns a fragile assumption (“these two will never overlap”) into a structurally guaranteed single point of control.

A wire with two drivers is legal Verilog with a precise resolution rule, which is what makes it dangerous: if the drivers agree, or one is correctly tri-stated whenever the other is active, nothing looks wrong until the combination where they disagree, which a test suite may never reach. The fix is structural: route competing drivers through explicit arbitration so exactly one signal drives a shared wire. In the next article, the last in the module, we pull every debugging technique from this section into one method for going from “the testbench says FAIL” to a confirmed root cause.

โ† Reset Domain Problems A Systematic Debugging Method โ†’