How to bind inner signals from DUT?

I am trying to binding DUT signals and manipulate them in the task.

I have a problem on how to bind two signals?
The code below shows bind dutI.A0 works fine. But I have difficulty to make it work on dutI.A1.

module A(input logic in);
  wire inner = in;
  always@* $display($time,, "%m: %0b", inner);
endmodule

module dut(input logic in);
  A A0(in);
  A A1(~in);
endmodule

//using task in interface to manipulate signals
interface bus (output wire inner);
  logic in1 = 'z;
  assign inner = in1;

  task set_inner(input logic value);
    in1 = value;
  endtask
endinterface

module tb;
  logic in;
  dut dutI(in);
  bind dutI.A0 bus m0(inner); //works on module A0
  //bind dutI.A1 bus m1(inner); //??????how to make it work also on A1
  initial begin
    in = 1;
    #1 bus.set_inner('0);
    #1 bus.set_inner('1);
    //???? Need to set_inner for A1
    #1 $finish;
  end
endmodule

Your bind is correct, but the way you are calling set_inner is not correct here. Binding is like secretly instantiating a module/interface within another RTL file without disturbing the existing code. The binded module/interface is instantiated directly into the target module.

Referring to IEEE 1800-2012 example, the cpu is module name, cpu1 is the instance name to which you want to bind the module fpu_props, The instance name of fpu_props is fpu_rules_1.

bind cpu: cpu1, cpu2, cpu3 fpu_props fpu_rules_1(a, b, c);

So, the heirarchy goes like this:

cpu1.fpu_rules_1.<some_signal_in_fpu_props>
cpu2.fpu_rules_1.<some_signal_in_fpu_props>
//... and so on ...
//... in your case, the heirarchy goes as follows:
dutI.A0.m0.set_inner('0);
dutI.A1.m1.set_inner('1);

When you bind to multiple instances, the full hierarchical path changes. As a result, you need to have dutI.A0.m0 and dutI.A1.m1 as calling path.

I have modified your code as below. It runs fine on EDAPlayground with VCS, while Riviera seems to have some simulator specific issues (discussed here and here).

module A(input logic in);
  wire inner = in;
  always@* $display($time,, "%m: %0b", inner);
endmodule
 
module dut(input logic in);
  A A0(in);
  A A1(~in);
endmodule
 
//using task in interface to manipulate signals
interface bus (output wire inner);
  logic in1 = 'z;
  assign inner = in1;
 
  task set_inner(input logic value);
    in1 = value;
    $display("in1 for %m is %0p",in1); // Debug: Full path display
  endtask
endinterface
 
module tb;
  logic in;
  dut dutI(in);
  bind dutI.A0 bus m0(inner); // Binded instance name is m0 for type bus
  bind dutI.A1 bus m1(inner); // Binded instance name is m1 for type bus
  initial begin
    in = 1;
    #1 dutI.A0.m0.set_inner('0); // use hierarchy as m0 is binded. Instantiated within A0
    #1 dutI.A1.m1.set_inner('1); // use hierarchy as m1 is binded. Instantiated within A1
    #1 $finish;
  end
endmodule

The CummingsSNUG2009SJ_SVA_Bind Paper is a good source of information for understanding about bind in SystemVerilog.

In reply to sharvil111:

Thanks sharvil111’s excellent reply. Now it can be running without error.

One purpose of this code is to force dutI.A0.inner and dutI.A1.inner in a nice way instead of hard-coding path hierarchy.

However, I just realize that it doesn’t work as what I am intent.

For same reason, dutI.A1.m1.set_inner didn’t affect dutI.A1.inner? Can somebody explain why?
Or is it possible to drive internal signals via binding?

In reply to mlsxdx:

This is the result after changing the binding.
0 tb.dutI.A0: x
0 tb.dutI.A1: x
0 tb.dutI.A0: z
1 in1 for tb.dutI.A0.m0.set_inner is 0
1 tb.dutI.A0: 0
2 in1 for tb.dutI.A1.m1.set_inner is 1

@1, tb.dutI.A0: 0 //works as expected
@2, tb.dutI.A1.m1.set_inner //works as expected, the set_inner task get called

Why the always $display not executed?
always@* $display($time, “%m: %0b”, inner);

In reply to mlsxdx:

One purpose of this code is to force dutI.A0.inner and dutI.A1.inner in a nice way instead of hard-coding path hierarchy.

One alternative is to take the signal as output, which you want to force all the way out to the top module. But this seems quite fuzzy.

Why the always $display not executed?
always@* $display($time, “%m: %0b”, inner);

The block is executed, showing output as follows:

always@* $display($time,, "Hiiii: %m: %0b", inner);
// Output:
                   0 Hiiii: tb.dutI.A0: 1
                   0 Hiiii: tb.dutI.A1: 0
in1 for tb.dutI.A0.m0.set_inner is 0
                   1 Hiiii: tb.dutI.A0: x
in1 for tb.dutI.A1.m1.set_inner is 1
                   2 Hiiii: tb.dutI.A1: x

I think we should focus on the note:

Ports coerced to inout
testbench.sv, 14
“inner”
Port “inner” declared as output in module “bus” may need to be inout.
Coercing to inout.

Seems like we are driving inner from in as well as from the output of bus interface. Ultimately, the bus** interface, inner signal is driven from set_inner task, which may cause multiple drivers to inner in tb.dutI.A0 ** module. This is just a guess for discussion, no rigid theory behind this.