Can modports be used to isolate logic signals in an interface from tasks?

I am trying to use SystemVerilog to simulate an IP block written in VHDL. As part of the SystemVerilog testbench, I have written a bus functional model to mimic a streaming AXI-4 bus using an interface with modports. I want to reuse the interface in several places within the testbench so the modports define an initiator, target, tb_initiator and tb_target, where the latter two modports import tasks to read and write packets for use as stimulus/validation. The idea is to use one bus functional model for connections between components as well as the external interfaces, where only the external interfaces would be driven by tasks.

interface axi_if();
  logic tlast, tvalid, tready;

  modport initiator();
  modport target();
  modport tb_initiator(..., import write_packet);
  modport tb_target(..., import read_packet);

  task write_packet();
  endtask

  task read_packet();
  endtask
endinterface

module top()

  axi_if tb_stim(clk);
  axi_if intercomponent(clk);
  axi_if tb_verif(clk);

  axi_component component1 (.clk(clk), .target(tb_stim), .initiator(intercomponent));
  axi_component compotent2 (.clk(clk), .target(intercomponent), .initiator(tb_verif));

endmodule

module axi_component(axi_if.target, axi_if.initiator)
endmodule

I seem to encounter one warning and one error in Questa 10.4 with this approach. Both are related in nature to multiple drivers, where the gist is that the tasks are writing to signals that are also driven externally. Although it is a warning for most cases, it becomes an error when I have to continuously assign a signal externally to the interface. “Variable written by continuous and procedural assignments” Obviously, the intention is not to use a task to drive the signal when the continuous assignment is used as this tends to be between components.

For example, the VHDL component has each signal presented individually on its interface necessitating the need to wire up the interface manually. I have also tried writing a wrapper around the VHDL component so that modports can be used at the top level but to no avail.

A simple solution is to create three separate interfaces; a target and initiator BFM for simulation and a separate BFM for inter-component connections. However, it seems that this should be the point of a modport. Is it possible to write a single interface to cope with the various scenarios? If so, how can the scope of the task be limited to the modports of interest only?

The modports are not the issue here. Modports are only used to restrict access to signals and tasks from certain connected modules. Your code has to be semantically and functionally correct with or without the modports.

The problem, as you have discovered, is rooted in the fact that these tasks try to procedurally assign signals that are also continuously driven. The rule about not mixing procedural and procedural assignments is very basic and the compiler is under no obligation to determine that the procedural assignments inside the tasks never get executed.

You can solve this problem by changing all interface signals that have this problem to wires, and dealing with them as if they were bi-directional signals. See my DVCon paper on this at The Missing Link: The Testbench to DUT Connection | Technical Paper | Verification Academy

In reply to dave_59:

Ah, so modports don’t affect the scoping of tasks. That would be the root of my confusion. Thanks for the link to your paper. Section 5, A looks to be immediately useful and there are a few other gems in there that will be handy.