Skip to content
VLSI Mentor

I²C · Module 20

Active and Passive Components — Agents, Monitors and Responders

The boundary that decides reuse on a wired-AND bus: which components may pull a line low. Argues that passivity has to be a property of a port list rather than a promise, and works through the common-mode failure that makes a monitor sharing the design's framing logic incapable of detecting a framing bug.

On a bus where the only action available is pulling a line low, the question "which components are allowed to do that" is the whole of the architecture. Everything else — reuse, trust, what a passing result means — follows from the answer.

There are exactly two kinds of component, and the boundary is not a matter of layering or of who talks to whom. It is physical.

1. The Distinction

Active. Pulls SCL or SDA low, at times of its own choosing. It changes what happens.

Passive. Reads the resolved lines and nothing else. It cannot change what happens, and that is not a restriction to be worked around — it is the entire source of its value.

This module's environment has four components on the bus. Three are active by design and one is passive by construction.

componentroledriveschapter
controller drivergenerates transfers, owns SCLSCL, SDA20.5
responder modelbehaves like a target deviceSCL (stretching), SDA20.6
fault injectorcreates conditions that should not occurSCL, SDA20.9
monitorreports what happenednothing20.7

The fault injector is worth noticing in that list. It is active, deliberately, and being a third participant on a shared bus rather than a hook inside the design is what makes it able to produce conditions the design has to survive. Injection is not observation and does not belong in a monitor — the two are opposite in exactly the sense this chapter is about.

2. Why the Distinction Decides Reuse

A passive component can be instantiated anywhere without changing the result. That is a strong statement and it has three consequences that are each worth more than they sound.

It can watch a bus it was not written for. The monitor in this module is verified against synthetic traces in mon_selftest, where a bench drives the lines directly and there is no design at all. The same module, unmodified, then watches the real Module 18 target in env_vs_dut against three HDL implementations. Same file, four situations, because it has no opinion about who else is present.

Two of them can coexist. Two monitors on one bus is harmless. Two drivers is a design decision about arbitration.

It can be left in. A monitor is safe in a regression, in a debug build, and in a system-level simulation where the real controller is somebody else's code. An active component in the same place is a second master.

3. Passivity Must Be Structural

A component is not passive because its author intended it to be. It is passive if it has no output that reaches the bus, and that is a property of a port list — checkable by reading fourteen lines, and impossible to violate by accident later.

Azvya Education Pvt. Ltd.VLSI Mentor
Abbreviated from i2c_mon.sv — the port list is the enforcement mechanism
   module i2c_mon #(
      parameter int MAX_BYTES = 8
   ) (
      input  logic clk,
      input  logic rst_n,

      // ---- the only inputs: the RESOLVED bus -----------------------------------
      input  logic scl,
      input  logic sda,

      // ---- everything else is an observation -----------------------------------
      output logic saw_start,
      output logic saw_restart,
      // ... and no scl_drive_low, no sda_drive_low, no output enable, anywhere
   );

There is no drive_low output and no output enable. Adding one later would be a port-list change visible in a diff, which is the point: the property survives maintenance by people who have not read this chapter.

Compare the two enforcement styles honestly, because the weaker one is extremely common:

approachfails how
a passive parameter guarding drive statementsworks, until the guard is inverted, defaulted wrongly, or one drive statement is written outside it
"the monitor does not drive" in a commentis true when written
no drive port existscannot fail without a port-list change

The bench asserts it anyway. With every active component released, both lines must read high — if the monitor were pulling anything, they would not:

Azvya Education Pvt. Ltd.VLSI Mentor
Abbreviated from i2c_mon_tb.sv — T9, asserting a structural property
         // T9. THE MONITOR NEVER DRIVES. Its port list has no output that reaches the
         //     bus, so this is structural rather than dynamic -- but the bench asserts
         //     it anyway, because the property is the reason the component is trusted.
         do_reset;
         @(negedge clk); scl_low = 1'b0; sda_low = 1'b0;
         for (n = 0; n < 20; n = n + 1) step;
         ck("T9 SCL reads high", scl, 1);
         ck("T9 SDA reads high", sda, 1);

Asserting a structural property looks redundant and is not. It documents why the component is trusted in a place where a future reader is looking, and it converts a silent architectural regression into a failing test.

4. The Failure That Makes a Monitor Worthless

Passivity is necessary and it is not sufficient. A monitor can drive nothing, be instantiated correctly, and still be incapable of detecting the defect it exists to detect — if it derives its answer from the same code as the thing it is watching.

The same argument applies to the responder, one step out. A responder model written by copying the target's register logic will agree with the target about every register decision, including the wrong ones. This is why 20.6's responder has a deliberately thin policy — acknowledge or not, stretch or not, a read base — rather than a second register file: a thin model has less opportunity to share a mistake.

A block diagram with a shared bus in the centre. Above the bus, three active components — the controller driver, the responder model and the fault injector — each connect to the bus with a bidirectional pull-low arrow, as does the Module 18 target. Below the bus, the monitor connects with a single arrow pointing away from the bus into a transaction assembler, indicating observation only.Controllerdriveractive · 20.5Respondermodelactive · 20.6Fault injectoractive · 20.9Targetactive · Module 18Resolved SCLand SDAwired-ANDMonitorPASSIVE · 20.7Assembler20.3pull lowread onlybytes12
Figure 1 — four participants, one shared pair of wires. Every solid arrow into the bus is a pull-low capability; the monitor has none, which is why its arrow points only outward. The dashed line marks the boundary that matters: everything above it can change the trace, and the one component below it cannot. Note that the injector sits above the line on purpose — injection is an active operation and does not belong in an observer.

5. What Active Components Owe

An active component is a participant, which means every protocol rule the environment checks of others applies to it as well. This is stated often and obeyed rarely, because the driver is the component with a reason to cheat: it knows what it is trying to do and the shortcut usually works.

The rule that matters most is the data-valid rule. SDA may change only while SCL is low, except for framing. A driver that changes SDA while SCL is high has generated a START or a STOP, whatever it intended, and a target that reacts accordingly is correct.

Two more obligations, both of which this module got wrong at first and fixed:

Every wait must be bounded. An active component that waits for the bus to do something must have a timeout, because the bus may never do it. The driver's stretch wait has STRETCH_TIMEOUT; 20.9's T5 exists to prove the timeout works by holding SCL low forever, and T5b exists to prove the driver waits when the hold is short. Without the second test the first one is evidence that the driver gives up, which is not the property wanted.

Releasing is not the same as being high. After releasing a line, an active component must read the resolved value before acting on it. Somebody else may still be holding it — that is what a stretch is — and a driver that assumes its own release took effect clocks data into a target that has stopped listening.

6. One Module, Two Situations

The clearest demonstration that passivity buys reuse is that the monitor in this environment is run in two structurally different situations without modification.

The monitor that acknowledged on the design's behalf

Pitfall — a monitor that drives, added for a reason that sounded good
Buggy Code
// A monitor for an I2C target. During bring-up the target's acknowledge logic
// was not ready, so the monitor was given a small helper so that transfers
// would complete and the rest of the environment could be developed:
//
//    module i2c_mon (
//       input  logic scl, sda,
//       output logic sda_drive_low,          // <-- "temporary"
//       output logic byte_valid, ...
//    );
//       // acknowledge on the target's behalf so transfers complete
//       always @(posedge clk)
//         sda_drive_low <= (bitcnt == 4'd8) && addr_matched;
//
// Eight months later the target's acknowledge logic is finished and works. The
// helper is still there. Nobody remembers it, because nothing fails.
//
// Then a customer reports that the part NACKs its own address intermittently.
// In simulation it never does -- because on every transfer TWO devices pull SDA
// low in the acknowledge slot, and the wired-AND of a correct acknowledge and
// the monitor's acknowledge is indistinguishable from a correct acknowledge
// alone. The environment cannot see a missing ACK. It has been supplying one.
Pitfall — the monitor that shared the design's framing detector
Buggy Code
// Sensible-looking reuse. The target already has a verified framing detector,
// so the monitor instantiates the same module:
//
//    i2c_framing fr_dut  (.scl(scl), .sda(sda), .start(dut_start), ...);  // in the DUT
//    i2c_framing fr_mon  (.scl(scl), .sda(sda), .start(mon_start), ...);  // in the monitor
//
//    // the scoreboard's framing check
//    if (dut_start !== mon_start) error("framing mismatch");
//
// This check can never fire. Two instances of one module, given identical
// inputs, produce identical outputs -- always, including when the module is
// wrong. The framing detector has a bug: it treats an SDA fall as a START
// without requiring SCL to be high. Both instances have it. They agree.
//
// The environment reports a clean framing check on a target that starts
// transfers on noise.

7. What 20.4 Settled

The boundary is physical: can this component pull a line low. Not layering, not who talks to whom. On a wired-AND bus that single capability is the whole of the difference.

Passivity must be structural. No drive port, so violating it requires a port-list change. A parameter or a comment is a promise, and promises do not survive bring-up.

Passivity buys reuse, and reuse is really about what a result means. The monitor here runs against synthetic traces with no design present and against three real target implementations, unmodified, because it cannot have influenced any of them.

Passive is not the same as independent. A monitor that shares framing code with the design agrees with it by construction, including in error. Independent reimplementation from the specification is the one correct duplication in this curriculum.

Active components owe the protocol everything they check of others. A driver that violates the data-valid rule produces failure reports against correct targets, and the usual outcome is a workaround in the design — a testbench bug that ships as hardware.

The next chapter builds the first active component, and it is the one with the most to obey: the driver owns SCL, decides when every bit moves, and has to follow every rule it is there to test. Chapter 20.5 — The Master Agent.

Continue learning