Skip to content
VLSI Mentor

I²C · Module 2

Wired-AND — Many Drivers, One Line, No Contention

Put several open-drain devices on one node and a logic function appears in the wiring: any participant asserting LOW wins, and HIGH requires unanimous release. Derive dominant LOW, see why two devices pulling together is agreement rather than conflict, and watch three of the protocol's mechanisms become predictable.

Chapter 2.4 completed the node: strong switched paths to ground from every attached device, one weak permanent path to the supply. Everything needed is now present, and this chapter does nothing but put more than one device on it and read off the consequences.

That turns out to be the point at which the electrical architecture stops being a circuit fact and becomes the reason the protocol looks the way it does. Nothing new is introduced here. What emerges was already implied by Chapters 2.3 and 2.4 — and the bus's three most distinctive behaviours fall out of it.

1. Several Devices, One Node

Three devices share a conductor. Each can pull it LOW or release it. A resistor restores it when nothing is pulling.

Work through the combinations, and notice that there is nothing subtle to work out — each case is decided by the same rule.

All three release. No switch connects the conductor to ground, so the resistor is the only path anywhere. The conductor rises to HIGH.

One pulls, two release. One low-impedance path to ground exists, and it dominates the weak resistor. The conductor is LOW.

Two pull, one releases. Two parallel paths to ground. The conductor is LOW — no more LOW than before, and no differently.

All three pull. Three parallel paths. Still LOW.

The pattern is immediate and has no exceptions: the conductor is HIGH only when every device has released, and LOW whenever any device is pulling.

Three open-drain devices and one pull-up resistor all attach to the same shared conductor. Each device contributes either a switched low-impedance path to ground or no connection at all. The resistor contributes a permanent weak path to the supply. The conductor is LOW if any device switch is closed and HIGH only when all of them are open.Pull-up resistorweak — acts only whenunopposedShared conductorLOW if any switch is closedDevice Apulls, or releasesDevice Bpulls, or releasesDevice Cpulls, or releasesrestoresany wins12
Figure 1 — the shared node as a decision. Each device contributes either a switched path to ground or nothing at all, and the single weak path to the supply acts only when no switched path is present. Any one closed switch is sufficient to determine the level, which is why the node's behaviour does not depend on how many devices are pulling.

2. Asserting and Releasing, in Time

Three open-drain devices sharing one conductor

10 cycles
Ten intervals. The pull-low intention of three devices is shown above the resulting level on the shared conductor. The conductor is HIGH only in the intervals where all three devices have released. In two places a device releases while another is still pulling, and the conductor stays LOW, demonstrating that releasing does not raise the line.asserted by someoneasserted by someonefreefreetwo pulling — still just LOWtwo pulling — still justLOWA released, B holds — stays LOWA released, B holds — staysLOWall released — risesall released — risesdev_adev_bdev_csdat0t1t2t3t4t5t6t7t8t9
Figure 2 — three participants and the level that results. The conductor follows the rule exactly: LOW while any device is pulling, HIGH only in the intervals where all three have released. Notice the two intervals where a device releases and the line does not rise, because another device is still asserting it. Intervals are illustrative rather than protocol timing.

Two moments in that figure deserve attention.

At the interval where two devices pull together, nothing special happens. The conductor is LOW, exactly as it was when one device pulled. There is no conflict, no intermediate voltage, no current between the devices — both are sinks connected to the same ground, so they agree by construction. This is the case that would have been destructive with push-pull outputs and is completely unremarkable here.

At the interval where A releases while B is still pulling, the line does not rise. A has stopped contributing, and the level is now B's business. This is Chapter 2.3's "release is not drive HIGH" made visible, and it is the single most consequential observation in the module.

3. The Function in the Wiring

Write the behaviour as a table, treating "pulling" as a device asserting itself and reading off the conductor's level.

Device ADevice BDevice CConductor
releasedreleasedreleasedHIGH
pullingreleasedreleasedLOW
releasedpullingreleasedLOW
releasedreleasedpullingLOW
pullingpullingreleasedLOW
pullingpullingpullingLOW

The conductor is HIGH only on the row where every device released. In logic terms, if each device's released state is treated as a 1 and pulling as a 0, then the conductor's level is the logical AND of what every device is contributing — and no gate was fitted to compute it. The wiring does it.

That is why the arrangement is called a wired-AND: a logic function implemented by the connection itself rather than by a component.

The name is worth handling carefully, because it is a frequent source of confusion. Whether this looks like an AND or an OR depends entirely on which polarity you call true. Describe it in terms of released = 1 and it is an AND, as above. Describe it in terms of asserting = 1 and the same circuit reads as an OR — any device asserting produces an asserted bus. Both descriptions are correct about the same hardware, which is why arguing about the label is unproductive.

The physical behaviour is what to remember, and it survives every naming convention: any participant can force the conductor LOW; making it HIGH requires unanimous release. Some engineers call this dominant LOW, which has the advantage of describing the behaviour instead of a polarity convention.

4. Why Multi-Device Operation Is Now Safe

Step back and notice how thoroughly Chapter 2.2's problem has been dissolved rather than managed.

With push-pull outputs, the number of attached devices was a hazard: every additional device was another potential source of a supply-to-ground conflict, and safety depended on all of them behaving.

Here, the number of attached devices barely matters. Every device can do exactly one thing — connect the conductor to ground — and any number of them doing it simultaneously produces the same result as one doing it. There is no combination of participants, well-behaved or otherwise, that creates an undefined level or a current path between devices. A device that powers up in an unexpected state, mis-decodes something, or holds its switch closed when it should not, produces a bus that is stuck LOW. That is a functional problem, and a debuggable one, but it is not an electrical fault.

The cost, already paid in Chapter 2.4, is that the one path upward is weak and the rising edge takes time. That is the entire price of safe sharing, and the rest of the curriculum is the protocol living within it.

5. What the Protocol Builds From This

Everything so far has been electrical. This section is where it becomes clear that the protocol was not designed alongside the electrical layer but out of it.

Recall the capability from Chapter 2.3: a device that releases and then reads its own input learns whether anybody else is asserting the conductor. Combine that with dominant LOW and three distinct mechanisms become not just possible but nearly inevitable.

A device can answer on a line it does not own. Because any participant may pull the conductor LOW, a device that has just been sent something can assert the shared data line to indicate it is there and responding, without needing a dedicated wire and without the original sender having to stop using the bus. Module 7 develops what that answer means and exactly when it happens.

A device can ask for time. The clock line is shared and subject to the same rule, so a device that needs longer before the transfer continues can simply hold SCL LOW. Whoever is generating the timing will find the line not rising when released and will wait, because the line does not rise until every participant releases it. Module 12 develops this.

A device can discover that it has lost. A participant that releases the conductor and reads back LOW knows that somebody else is asserting it. In a system with more than one potential initiator, that observation is enough to detect that another initiator is active and disagreeing — using no extra signal, no arbiter and no negotiation. Module 13 develops this.

6. Modelling the Wired-AND Bus

The truth table in §3 is a claim about composition, and composition is exactly what a simulation can prove exhaustively. Three participants give eight combinations; all eight can be checked, so nothing is left to argument.

The model extends Chapter 2.3's participant to three instances on one conductor. Each participant contributes 0 or high impedance and nothing else, one weak pull-up establishes HIGH, and each participant reads the resolved line back separately — because the whole point is that what a participant contributes and what it observes are different quantities.

6a. SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
wired_and_bus.sv — three open-drain participants on one conductor (SIMULATION MODEL of a bus)
   // Models the board-level conductor and its pull-up. Not synthesizable RTL:
   // synthesis builds neither a wire nor a resistor. The synthesizable part is
   // each participant's drive_low intent plus the I/O cell (Chapter 2.3, §6-§7).
   module wired_and_bus (
       input  logic [2:0] drive_low,   // one bit of intent per participant
       output logic [2:0] sees,        // what each participant reads back
       output wire        line         // the resolved conductor -- a NET
   );
       pullup (line);                  // weak 1: establishes HIGH only when unopposed

       // Three independent open-drain contributions. Each is 0 or high-impedance;
       // none can contribute 1. Net resolution does the wired-AND for free.
       assign line = drive_low[0] ? 1'b0 : 1'bz;
       assign line = drive_low[1] ? 1'b0 : 1'bz;
       assign line = drive_low[2] ? 1'b0 : 1'bz;

       // Observation is per-participant and separate from intent. All three read
       // the same conductor, so all three must agree with each other.
       assign sees = {3{1'b0}} | {line, line, line};
   endmodule

Two SystemVerilog assertions state the invariant the chapter derived. They are deliberately small — Module 21 owns the protocol assertion library; these two are about the electrical contract.

One detail in them is worth more than the invariants themselves, because it is a trap that catches people writing their first checker on a resolved net. A plain immediate assertion inside always_comb re-evaluates the instant any input changes — including partway through a time step, before a multi-driver net has finished resolving. Set drive_low to 001 and, for a vanishingly short moment, the simulator has seen the new intent but not yet the new line value, so a plain immediate assertion reports a violation that never really existed. The fix is a deferred immediate assertion, written assert #0, which postpones evaluation to the end of the time step and therefore judges only settled values. That is what these use.

Azvya Education Pvt. Ltd.VLSI Mentor
wired_and_props.sv — the two electrical invariants (CHECKER; needs deferred-assertion support)
   // Bound to the bus model. Both properties are immediate (combinational): the
   // conductor resolves within the same instant, so there is no clock here and
   // nothing to sample -- which is itself the lesson. These are electrical
   // invariants, not protocol timing.
   module wired_and_props (
       input logic [2:0] drive_low,
       input logic       line
   );
       // INVARIANT 1 -- ANY pull forces LOW. If any participant requests LOW, the
       // observed conductor must be LOW. This is what makes a single participant
       // able to assert the bus regardless of the others.
       //
       // `assert #0` is a DEFERRED immediate assertion: it is evaluated at the end
       // of the time step, after the multi-driver net has resolved. A plain
       // `assert` here would fire on the transient between "intent changed" and
       // "net settled" and report violations that do not exist.
       always_comb begin
           if (|drive_low)
               assert #0 (line === 1'b0)
                   else $error("any participant pulling must force the line LOW (drive_low=%b line=%b)",
                               drive_low, line);
       end

       // INVARIANT 2 -- observed HIGH implies UNANIMOUS release. The contrapositive
       // of invariant 1, and the more useful direction in practice: seeing HIGH is
       // evidence about EVERY participant, not just about this one.
       always_comb begin
           if (line === 1'b1)
               assert #0 (drive_low === 3'b000)
                   else $error("line is HIGH while a participant still pulls (drive_low=%b)", drive_low);
       end
   endmodule

The testbench walks all eight combinations, checks the resolved level against the wired-AND rule, and separately checks the property that matters most for later modules: a participant that has released still observes whatever the other participants are doing.

Azvya Education Pvt. Ltd.VLSI Mentor
wired_and_bus_tb.sv — exhaustive: all 8 combinations, plus release-and-observe
   module wired_and_bus_tb;
       logic [2:0] drive_low;
       logic [2:0] sees;
       wire        line;
       logic       expected;      // module scope: a block-scope variable WITH an
       int         errors = 0;    //   initialiser is static, so it would be set once

       wired_and_bus dut (.drive_low(drive_low), .sees(sees), .line(line));

       // The checker above is bound in a simulator that supports deferred
       // assertions. The testbench ALSO performs the same two checks below, after
       // settling, so the invariants are verified either way -- which is why the
       // sweep is written to check the resolved level explicitly rather than
       // relying on the assertions alone.

       initial begin
           // EXHAUSTIVE: three participants, eight combinations, no gaps.
           for (int unsigned c = 0; c < 8; c++) begin
               drive_low = c[2:0];
               #1;
               // The wired-AND rule: LOW if any bit set, HIGH only if none.
               expected = (c == 0) ? 1'b1 : 1'b0;
               if (line !== expected) begin
                   $error("drive_low=%b: line=%b expected %b", drive_low, line, expected);
                   errors++;
               end
               // One conductor means one observed level for everybody.
               if (sees !== {3{line}}) begin
                   $error("drive_low=%b: participants disagree (sees=%b line=%b)",
                          drive_low, sees, line);
                   errors++;
               end
               // INVARIANT 1 -- any pull forces LOW (checked after settling).
               if ((|drive_low) && line !== 1'b0) begin
                   $error("drive_low=%b: a participant pulls but line=%b", drive_low, line);
                   errors++;
               end
               // INVARIANT 2 -- observed HIGH implies unanimous release.
               if (line === 1'b1 && drive_low !== 3'b000) begin
                   $error("line is HIGH while drive_low=%b", drive_low);
                   errors++;
               end
           end

           // RELEASE AND OBSERVE -- the property the protocol is built on.
           // Participant 0 releases while participant 1 keeps pulling.
           drive_low = 3'b010; #1;
           if (sees[0] !== 1'b0) begin
               $error("participant 0 released but must still observe the held LOW"); errors++;
           end
           // Now participant 1 also releases: only then does the line rise, and
           // participant 0's observation changes without participant 0 doing anything.
           drive_low = 3'b000; #1;
           if (sees[0] !== 1'b1) begin
               $error("line must rise once every participant has released"); errors++;
           end

           if (errors == 0) $display("PASS: all 8 combinations obey wired-AND; release-and-observe works");
           else             $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule

6b. Verilog

Same conductor, same pull-up, same exhaustive sweep. Verilog has no concurrent-assertion construct, so the invariants are written as explicit procedural checks — which is a fair illustration of what SystemVerilog assertions buy: the same invariant, expressed once and checked continuously rather than at points the testbench remembers to look.

Azvya Education Pvt. Ltd.VLSI Mentor
wired_and_bus.v — three open-drain participants in Verilog (SIMULATION MODEL)
   module wired_and_bus (
       input  wire [2:0] drive_low,
       output wire [2:0] sees,
       output wire       line
   );
       pullup (line);

       assign line = drive_low[0] ? 1'b0 : 1'bz;
       assign line = drive_low[1] ? 1'b0 : 1'bz;
       assign line = drive_low[2] ? 1'b0 : 1'bz;

       assign sees = {line, line, line};
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
wired_and_bus_tb.v — exhaustive sweep with the invariants checked inline
   module wired_and_bus_tb;
       reg  [2:0] drive_low;
       wire [2:0] sees;
       wire       line;
       integer    c, errors;
       reg        expected;

       wired_and_bus dut (.drive_low(drive_low), .sees(sees), .line(line));

       initial begin
           errors = 0;
           for (c = 0; c < 8; c = c + 1) begin
               drive_low = c[2:0];
               #1;
               expected = (c == 0) ? 1'b1 : 1'b0;
               if (line !== expected) begin
                   $display("FAIL: drive_low=%b line=%b expected %b", drive_low, line, expected);
                   errors = errors + 1;
               end
               if (sees !== {3{line}}) begin
                   $display("FAIL: drive_low=%b participants disagree", drive_low);
                   errors = errors + 1;
               end
               // INVARIANT 1 -- any pull forces LOW.
               if ((|drive_low) && line !== 1'b0) begin
                   $display("FAIL: a participant pulls but line=%b", line);
                   errors = errors + 1;
               end
               // INVARIANT 2 -- HIGH implies unanimous release.
               if (line === 1'b1 && drive_low !== 3'b000) begin
                   $display("FAIL: line HIGH while drive_low=%b", drive_low);
                   errors = errors + 1;
               end
           end

           drive_low = 3'b010; #1;
           if (sees[0] !== 1'b0) begin
               $display("FAIL: released participant must observe the held LOW");
               errors = errors + 1;
           end
           drive_low = 3'b000; #1;
           if (sees[0] !== 1'b1) begin
               $display("FAIL: line must rise after unanimous release");
               errors = errors + 1;
           end

           if (errors == 0) $display("PASS: all 8 combinations obey wired-AND; release-and-observe works");
           else             $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule

6c. VHDL

The same composition through std_logic resolution, with the pull-up as a driver contributing weak 'H' exactly as in Chapter 2.3. VHDL's assert inside the stimulus process plays the role of both the checks and the invariants.

Azvya Education Pvt. Ltd.VLSI Mentor
wired_and_bus.vhd — three participants plus a weak-H pull-up (SIMULATION MODEL)
   library ieee;
   use ieee.std_logic_1164.all;

   entity wired_and_bus is
       port (
           drive_low : in  std_logic_vector(2 downto 0);
           sees      : out std_logic_vector(2 downto 0);
           line      : inout std_logic          -- resolved: four drivers total
       );
   end entity;

   architecture sim of wired_and_bus is
   begin
       -- The pull-up: weak 'H'. Beaten by any strong '0', beats 'Z'.
       line <= 'H';

       -- Three open-drain contributions. None can contribute '1'.
       line <= '0' when drive_low(0) = '1' else 'Z';
       line <= '0' when drive_low(1) = '1' else 'Z';
       line <= '0' when drive_low(2) = '1' else 'Z';

       -- Per-participant observation, normalised so 'H' reads as '1'.
       sees <= (others => to_X01(line));
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
wired_and_bus_tb.vhd — exhaustive sweep with assert report severity
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity wired_and_bus_tb is
   end entity;

   architecture sim of wired_and_bus_tb is
       signal drive_low : std_logic_vector(2 downto 0) := (others => '0');
       signal sees      : std_logic_vector(2 downto 0);
       signal line      : std_logic;
   begin
       dut : entity work.wired_and_bus
           port map (drive_low => drive_low, sees => sees, line => line);

       stim : process
           variable expected : std_logic;
       begin
           -- EXHAUSTIVE: all eight combinations of three participants.
           for c in 0 to 7 loop
               drive_low <= std_logic_vector(to_unsigned(c, 3));
               wait for 1 ns;

               if c = 0 then expected := '1'; else expected := '0'; end if;

               assert to_X01(line) = expected
                   report "wired_and_bus: resolved level disagrees with the wired-AND rule"
                   severity error;

               -- INVARIANT 1 -- any pull forces LOW.
               if drive_low /= "000" then
                   assert to_X01(line) = '0'
                       report "wired_and_bus: a participant pulls but the line is not LOW"
                       severity error;
               end if;

               -- INVARIANT 2 -- HIGH implies unanimous release.
               if to_X01(line) = '1' then
                   assert drive_low = "000"
                       report "wired_and_bus: line is HIGH while a participant still pulls"
                       severity error;
               end if;

               -- One conductor, so every participant observes the same level.
               assert sees = (sees'range => to_X01(line))
                   report "wired_and_bus: participants disagree about the conductor"
                   severity error;
           end loop;

           -- RELEASE AND OBSERVE.
           drive_low <= "010"; wait for 1 ns;
           assert to_X01(sees(0)) = '0'
               report "wired_and_bus: released participant must still observe the held LOW"
               severity error;
           drive_low <= "000"; wait for 1 ns;
           assert to_X01(sees(0)) = '1'
               report "wired_and_bus: line must rise after unanimous release"
               severity error;

           report "wired_and_bus self-check complete" severity note;
           wait;
       end process;
   end architecture;

6d. What the Three Models Establish

Ports, the number of participants, the pull-up, the stimulus sweep and the expected results are identical across the three. All eight combinations are checked in each, so the wired-AND rule is not asserted — it is demonstrated exhaustively.

The three differ only in how the invariants are expressed: SystemVerilog states them as concurrent assertions that hold continuously, Verilog as procedural checks the testbench performs at each step, and VHDL as assert statements inside the stimulus process. The first is stronger in a real environment, because a concurrent assertion fires on any violation whether or not the test was looking — which is the practical argument for assertions over checks, made concrete on a five-line property.

All of these are simulation models of a bus, not synthesizable designs. The conductor and the pull-up are board objects. What synthesises is each participant's drive_low intent and the I/O cell behind it, as Chapter 2.3 §6 and §7 established.

7. Verification Connection — What a Driver Controls and a Monitor Observes

The model above makes a verification architecture point that is easy to state and expensive to get wrong, and it is the reason this chapter matters to DV engineers as much as to designers.

A testbench component driving a participant on this bus must not assign the bus a value. It has no such capability — no participant does. What it controls is exactly one bit: this participant's drive_low intent. Whether the conductor is LOW or HIGH is then determined by every participant together, and the component finds out by reading the line like everyone else.

That splits cleanly into components, and the split is the lesson:

ComponentControlsObservesMust never
Driverits own participant's drive_lowthe resolved line, to know what happenedassign the bus a level, or assume its intent became the level
Monitornothingthe resolved line onlyreconstruct activity from any driver's intent
Scoreboardnothingreconstructed transfersassume the driver's request equals what appeared on the bus

The abstraction chain runs one way, and each arrow is a genuine translation rather than a rename:

Azvya Education Pvt. Ltd.VLSI Mentor
the abstraction chain -- each arrow is a translation, not a rename
   sequence item        "read two bytes from device 0x48"
        |               (intent, no conductors mentioned)
        v
   driver intent        drive_low asserted and released over time
        |               (this participant only)
        v
   electrical model     0 or high-impedance contributed to a shared net
        |
        v
   resolved bus         what the conductor actually became
        |               (every participant's contribution combined)
        v
   monitor              samples the resolved bus -- and nothing else
        |
        v
   reconstruction       "device 0x48 was read, two bytes, outcome X"

A concise illustration of the boundary, and the point is the comment on the forbidden line:

Azvya Education Pvt. Ltd.VLSI Mentor
illustrative only — what a participant driver may and may not do
   // Inside a driver for one participant on the shared bus.
   task automatic assert_low_for(int unsigned cycles);
       vif.drive_low <= 1'b1;               // CORRECT: request that this
       repeat (cycles) @(posedge vif.clk);  //   participant pull the line
       vif.drive_low <= 1'b0;               // CORRECT: release -- NOT drive HIGH

       // vif.line <= 1'b1;   // WRONG, and wrong in two separate ways:
       //   1. no participant can drive the line HIGH -- the capability was
       //      deliberately removed in Chapter 2.3;
       //   2. the line is a RESOLVED net, so a testbench assigning it is adding
       //      a driver the real system does not have, and the bug it hides is
       //      exactly the one a real bus would expose.
   endtask

Why this matters later, stated without teaching the mechanisms: a monitor that trusted driver intent instead of the resolved line would be blind to any situation where a participant's request and the bus disagree. Those situations are not exotic — they are precisely where clock stretching and arbitration live, which is why an environment built on the wrong abstraction cannot verify either. Modules 12, 13 and 19 to 21 develop all of it.

8. Debugging the Wired-AND Bus

The driver that verified its own wishes — trusting intent instead of the resolved bus

Pitfall — checking what we requested rather than what the conductor did
Buggy Code
// A testbench component for one participant. It asserts the line LOW for a while,
// then releases and immediately confirms the bus is HIGH again:
 drive_low = 1'b1;
 #100;
 drive_low = 1'b0;                 // release
 #1;
 if (drive_low == 1'b0)            // <-- THE BUG: checking our own intent
     pass_count++;                 //     and calling it a bus check
// The check is tautological. drive_low is an output of this component, so it
// always equals what we just wrote. The test passes on every run, including runs
// where the conductor never rose at all.
// A subtler variant of the same bug reads the line but only ever runs with ONE
// participant present, so intent and resolved level can never disagree.
Symptom

Green regressions, and hardware that fails the moment a second participant is active. The testbench reports full pass rates while never having observed the conductor. Symptoms in the lab: a controller that proceeds as though a line rose when another device is still holding it LOW, transfers that go wrong only when a particular peripheral is fitted, and behaviour that changes when devices are added or removed -- because the bug's severity depends on whether anybody else is contributing. On a single-participant bench it is undetectable by construction.

Root Cause

The component conflated DRIVE INTENT with OBSERVED LEVEL -- the two quantities Chapter 2.1 separated and this chapter exists to keep apart. On a dedicated output they are the same fact, and that habit is what leaks in. On a shared conductor the level is a function of EVERY participant, so a component can only learn it by reading the line. Checking drive_low proves that the component's own register holds what the component wrote, which is true unconditionally and therefore establishes nothing about the bus. The deeper failure is architectural rather than a typo: the environment had no component whose job was to observe the resolved conductor, so there was nothing for a scoreboard to compare against even if somebody had wanted to.

Fix
// Read the CONDUCTOR, and read it with a participant that is NOT us. Release,
// then observe what the shared line actually resolved to:
 drive_low = 1'b1;
 #100;
 drive_low = 1'b0;                 // release: stop contributing
 #1;
 if (line !== 1'b1)                // observe the RESOLVED conductor
     $error("released, but the line did not rise -- somebody is holding it");

// And the structural fix, which is the one that matters: the environment needs a
// passive monitor that samples ONLY the resolved line and never any driver's
// intent. Then a scoreboard can compare requested activity against observed
// activity -- and the two disagreeing becomes a finding rather than an
// invisibility.
//
// The test that catches the original bug: run with a SECOND participant holding
// the line LOW while the first releases. Intent says released, the conductor says
// LOW, and any check built on intent passes while any check built on observation
// correctly fails. A single-participant bench can never expose it, which is why
// multi-participant stimulus is not an optional extra on a shared bus.

The engineering lesson generalises past this bus. On any shared medium, a component's own output is never evidence about the medium's state. The question a reviewer should ask of any testbench for a resolved net is simply: which signal is this check reading? If it reads something the testbench itself drives, the check is tautological — it will pass forever and prove nothing. If it reads the resolved conductor, it can fail, and a check that can fail is the only kind worth having.

9. Common Misconceptions

10. Reason It Through

Work this through before reading the answers.

Two devices share SDA. Device A releases the line and immediately reads its own input. It reads LOW. Device B is pulling.

What has A learned? That another participant is asserting the conductor. A contributed nothing after releasing, so the LOW cannot be its own doing.

Could A have learned this if the bus used push-pull outputs? No, and the reason is structural rather than practical. With push-pull, A releasing is not a state that exists — A would be driving HIGH, which means opposing B's LOW, which is the electrical fault from Chapter 2.2. The observation is only available because "release" is a real state in which the device still has a working input while contributing nothing.

Why does this matter for a system with two potential initiators? Because it gives a participant a way to detect that another one is active and disagreeing, without an arbiter, an extra signal or any negotiation — just an ordinary input and the dominant-LOW rule. Module 13 turns that into a complete mechanism, including what the losing participant must do.

What would A conclude if it released and read HIGH? That nothing else is currently pulling — subject to one qualification that the next chapter develops. Reading immediately after releasing can be misleading, because the line does not reach HIGH instantly: it has to charge through the pull-up. A device that samples too soon can read LOW simply because the edge has not finished, not because anyone is asserting. The distinction between "held LOW by a participant" and "still rising" is exactly the kind of judgement Chapter 2.6 is about.

11. Understanding Check

12. Summary

Put several open-drain devices on one node with a pull-up and a rule appears that has no exceptions: the conductor is LOW whenever any device is pulling, and HIGH only when every device has released.

That is a logic function implemented by the wiring rather than by a component — a wired-AND when released is treated as 1, a wired-OR under the opposite convention, and dominant LOW under either. The label is a polarity choice; the behaviour is not.

Several devices pulling together is agreement, not conflict. All are sinks to the same ground, no current flows between them, and the result is identical to one device pulling. The hazard from Chapter 2.2 does not scale with device count because no participant has the capability required to create it.

Releasing is not raising. A device that releases while another still pulls sees the line stay LOW — and a device that releases and reads its own input thereby learns whether anybody else is asserting. That single capability, combined with dominant LOW, is what the protocol's three most distinctive mechanisms are made of: answering on a shared line, holding the clock to ask for time, and detecting another active participant by releasing and observing. They are applications of the electrical architecture, not features layered on top of it.

It also changes what a testbench must model: not what a device drove, but the resolved level every participant contributed to.

13. What Comes Next

The model is complete and idealised. Chapter 2.6 closes Module 2 by putting it on a real board — where edges have shape, levels are thresholds rather than values, the capacitance comes from identifiable places, and two devices may not even share a supply voltage. That is also where the difference between "held LOW by a participant" and "still rising" becomes a judgement an engineer has to make while looking at a capture.

Browse the full path on the I²C tutorials index.

Continue learning