How can I resolve virtual interface wire problem?

SystemVerilog requires that only ‘wire’ can connect with inout.

If I define interface signal with wire property. and I want to drive it in my class task by virtual interface. But This is not supported by simulator.

Please see code below:

interface bidir(input clk);

  **wire** dp, dm;
  
endinterface:bidir

class usb ;
  virtual bidir v_if;
  function new(virtual bidir v_if);
    this.v_if = v_if;
  endfunction:new

  task run();
    v_if.dp = 0;
    v_if.dm = 0;
    forever begin
      @(posedge v_if.clk) begin
	v_if.dp = ~v_if.dp;
	v_if.dm = v_if.dm;
      end
      @(posedge v_if.clk) begin
	v_if.dp = 'z;
	v_if.dm = 'z;
      end
    end
      @(posedge v_if.clk) begin
	v_if.dp = $random;
	v_if.dm = $random;
      end
  endtask:run
  
endclass


module submod(/*AUTOARG*/
  // Inouts
  dp, dm
  );
  inout dp, dm;

  wire 	dp_i = dp;
  wire 	dm_i = dm;

  bit 	oe;

  bit 	dp_o, dm_o;
  
  assign dp = oe ? dp_o : 'z;
  assign dm = oe ? dm_o : 'z;
	      
endmodule


module top;
  bit clk;

  
  bidir bidir(clk);

  
  submod submod(/*AUTOINST*/
		// Inouts
		.dp			(bidir.dp),
		.dm			(bidir.dm));
  
  always #5 clk = ~clk;

  initial begin
    usb usb = new(bidir);
    fork
      usb.run;
    join_none

    $display("================\n**************\n===============\n");
    #100 $finish;
  end
  
  
endmodule:top

It can’t run in Cadence simultor. but it is supported by Synopsys Simultor.

How can I changed the code to run in all simulators.

Your simulator has a bug in it if it allows you to procedurally assign to a wire. How long is the value that is procedurally assigned supposed to stay on the wire? And don’t you want to see X’s if the submodule output enable is active at the same time as your TB tries to drive a value?

You have two LRM compliant choices when dealing with testbench connections to inouts

  1. use a continuous assignment in your interface just like in your submodule assign dp =dp_o; If you declare dp_o as a logic, there’s no need for a separate oe signal. You just set do_o to 'z when you don’t want it to drive.
  2. If your signals are synchronous, you can use a clocking block. That might not work for you in this case, but it’s something to remember.

In reply to dave_59:

Dear Dave:

   Thanks for your kindly reply.

   But I don't think it is a bug of simulator to permit procedure assignment. I think it is the bug of simulator not to allow procedure assignment instead.
   First I assign the signal to 'z, and when i dont want to drive it, i can set it to 'z again. The whole driving duration is defined by code, Engineers can take control of the behavior. It is not supposed  for the simulator to check this, it can be guaranteed by engineers. 

  I do not think it is a good idea to forbid such behavior in simulators, to avoid this issue a lot more coding must added to solve this problem, and the code is not good for read in the future.

In reply to Jules:

Allowing a procedural assignment to a wire “appears” to work in some cases. It won’t work if there are pullups or any other strengths driving the wire. It won’t work when creating EVCD files or any other formats that need to know whether the DUT or TB is actively “driving”.

The problem is that the behavior of a wire is defined by a built-in resolution function using the values and strengths of all the drivers(continuous assignments) to a wire. Every time there is a change on one of the drivers, the function is called to produce a resolved value. The function is based on the kind of wire type, wand, wor, tri1, etc.

If you are allowed to make a procedural assignment to a wire, you are bypassing that resolution function to put whatever value you wanted on the wire. If you make a procedural assignment to a wire with the value 'z, the wire goes to 'z. If the resolution of the other drivers would have produced a 0 or 1, you won’t see a change on the wire until one of the other drivers has a change. For example

wire w;

assign (weak1,weak0) w = 1;
initial begin
// time 0 w is 1
#1 w = 0; // w goes to 0
#1 w = 'z; // w goes to z
#1 // w stays at z, never goes back to 1
end

In reply to dave_59:

Dear Dave:

  I only use wire procedure assignment in dynamic object which has a member of virtual interface. and I connect the physical interface with virtual interface. In dynamic object procedure code, only 0,1,z,x can be assigned to the virtual interface, there must not be any continuous assignment in dynamic object. So the error you mentioned can not occur.

  Incidentally I use pulldown (weak0) (bidir.dp) in physical interface, it really work well in Synopsys simulator VCS.  The code is below. 
module top;
  bidir bidir(clk);
**pulldown (weak0) usb_dp (bidir.dp);
  pulldown (weak1) usb_dm (bidir.dm);**   
endmodule;

I have found a method to solve the problem.

First:
Modify Interface as below, add 2 extra signals.

interface bidir(input clk);

    logic dp;
    logic dm;
    wire wire_dp;
    wire wire_dm;
   assign wire_dp = dp;
   assign wire_dm = dm;
 
endinterface:bidir

Second:
In class task method(dynamic object), use dp|dm to procedure assignment and use wire_dp|wire_dm to sample.

But the code seems wierd.

In VCS, just define dp/dm as wire type, and procedure assignment and sample both can use the same net dp/dm. The interface is much more like the real environment(only 2 signals, not 4 signals).