Multiple drivers on I2C bus

I have two different types of interfaces that are both trying to drive a signal to the dut on an I2C interface:

interface i2c_if_type0 (inout wire sda, inout wire clk);
    logic sda_in, sda_out, clk_in, clk_out;
    // skip cod for out_cfg which set's the output in either push-pull or in open-drain
    assign sda = out_cfg ? sda_out : (sda_out ? 1'bz : 0);
    assign clk = out_cfg ? clk_out : (clk_out ? 1'bz : 0);
    sda_in = sda;
    clk_in = clk;
endinterface

interface i2c_if_type1 (inout wire sda, inout wire clk);
    logic sda_in, sda_out, clk_in, clk_out;
    assign (weak1, strong0) sda   = sda_out;
    assign (weak1, strong0) clk   = clk_out;
    sda_in = sda;
    clk_in = clk;
endinterface

module tb;
    wire i2c_sda;
    wire i2c_clk;

    // instantiate the interfaces
    i2c_if_type0 i2c_if0();
    i2c_if_type1 i2c_if1();

    // I thought of using 'tran' in order to pass the resolution to the interface pins as well
    tran u_tran0 (i2c_if0.sda, i2c_sda);
    tran u_tran0 (i2c_if1.sda, i2c_sda);
    tran u_tran0 (i2c_if0.clk, i2c_clk);
    tran u_tran0 (i2c_if1.clk, i2c_clk);

    // here it should be sufficient to specify the strength, believing that i2c_sda and i2c_clk are
    // going to be declared at some point.
    pullup (pull1) pullup_sda (i2c_sda);
    pullup (pull1) pullup_clk (i2c_clk);
    
    // connectivity to dut
    dut u_dut(
      .sda(i2c_sda),
      .clk(i2c_clk)
    );

endmodule

Well, everything works just fine as long as the DUT doesn’t try to drive the PAD. The PAD turns ‘x’ when it’s being driven by the dut. I suspect something to do with the ‘tran’ not correctly propagating 1’bz value… maybe the strength of the signals is messed up. It’s something that should be trivial, I’ve just spent too much time looking at it and now I’m highly biased.

Any idea?

You declared sda as an interface port, why not use it? No need for tran primitives.

What mode is out_cfg in when the DUT is driving?

What code is the DUT using to drive i2c_sda?

You declared sda as an interface port, why not use it? No need for tran primitives.

you are totally right, here’s the new code:

module tb;
    wire i2c_sda;
    wire i2c_clk;

    // instantiate the interfaces
    i2c_if_type0 i2c_if0(.sda(i2c_sda), .clk(i2c_clk));
    i2c_if_type1 i2c_if1(.sda(i2c_sda), .clk(i2c_clk));

    // here it should be sufficient to specify the strength, believing that i2c_sda and i2c_clk are
    // going to be declared at some point.
    pullup (pull1) pullup_sda (i2c_sda);
    pullup (pull1) pullup_clk (i2c_clk);
    
    // connectivity to dut
    dut u_dut(
      .sda(i2c_sda),
      .clk(i2c_clk)
    );

endmodule

What mode is out_cfg in when the DUT is driving?

you can assume out_cfg is statically set at reset (and in the current case it’s 1'b0).

What code is the DUT using to drive i2c_sda ?

After some digging, it looks like the driver is this one:

// while dut is driving a 0 on PUi the PAD goes to 1'bx
bufif1 (weak0, weak1) (PAD, 1'b1, PUi);

so on the i2c_sda we end up with one driver setting a weak0 while the i2c_if0 is driving a highZ and the pullup is driving a pull1. I’m not entirely sure who wins between weak0 and pull1… Is that going to lead to an 1'bx ?

Change how you model open drain in i2c_if_type1

After some further investigation, I’ve realized the driver for i2c_if_type0 was driving sda_out to 1'bz, which led the assignment go to 1'bx, so I had to make the following change:

interface i2c_if_type0 (inout wire sda, inout wire clk);
    logic sda_in, sda_out, clk_in, clk_out;
    // skip cod for out_cfg which set's the output in either push-pull or in open-drain
    // assign sda = out_cfg ? sda_out : (sda_out  ? 1'bz : 0);
    // assign clk = out_cfg ? clk_out : (clk_out ? 1'bz : 0);
    assign sda = out_cfg ? sda_out : (sda_out === 0 ? 0 : 1'bz);  // <----
    assign clk = out_cfg ? clk_out : (clk_out === 0 ? 0 : 1'bz);  // <----
    sda_in = sda;
    clk_in = clk;
endinterface