Assigning interface net-type signals from class

good day!
i need to assign interface net-type signals:

interface lb_if (input bit clk, reset);
wire [54:0] data;
wire start;
modport SRC_BUF (
inout data, start,
input clk, reset
);

endinterface : lb_if

from class’s task:

class Driver_lb;

virtual lb_if.SRC_BUF lb_bus;
bit [54:0] data;
bit start;

function new(
virtual lb_if.SRC_BUF lb_bus,
mailbox mbx_genlb_drvlb,
bit [54:0] data,
bit start,
bit grant
);
this.lb_bus = lb_bus;
this.data = data;
this.start = start;
endfunction : new

task someone_else_on_the_bus_for_one_package ();
start = 1’b1;
for (int k = 0; k < WORDS_IN_PACKET; k++)
begin
data = lb.trash_to_fill_lb [k];
repeat (3) @(posedge lb_bus.clk);
if (k == 0)
begin
start = 1’b0;
end
end
repeat (50) @(posedge lb_bus.clk);//пауза между пакетами
endtask: someone_else_on_the_bus_for_one_package

endclass: Driver_lb

this is just a snippet of program. Of course, net-type signals can not be assigned inside a cycle, so i tried diffrent ways of solving this problem: i tried to assign them in the top-level of my design (in function “new” signals “start” and “data” are made external to the class), but it only assigned once in the beginning of my simulation:

assign lb_bus.data = data;
assign lb_bus.start = start;

I tried to write smthng like this:

class Driver_lb;

virtual lb_if.SRC_BUF lb_bus;
bit [54:0] data;
bit start;
function new(
virtual lb_if.SRC_BUF lb_bus,
mailbox mbx_genlb_drvlb
);
this.lb_bus = lb_bus;
this.mbx_genlb_drvlb = mbx_genlb_drvlb;
this.data = lb_bus.data;
this.start = lb_bus.start;
endfunction : new

bus signals are supposed to be assigned in “new” function, but it didn’t work too: bus signals were in Z-state.
There must be an easy solution, but i just can’t see it… Would appreciate you help

One approach is to use 2 modports, one for input and one for output. That way the data and start signals do not have to be nets.

interface lb_if (input bit clk, reset);
  logic [54:0] data;
  logic start;

  modport SRC_BUF_I (
    input data, start,
    input clk, reset
  );
  
  modport SRC_BUF_O (
    output data, start,
    input clk, reset
  );

endinterface : lb_if

hi Trogers:
Do you think below method can be used in your case?
interface lb_if (input bit clk, reset);
wire [54:0] data;
wire start;
logic data_lb = 'z;
logic start_lb = 'z;

assign data = data_lb;
assign start= start_lb;

modport SRC_BUF (
output data_lb, start_lb,
input clk, reset
);
endinterface : lb_if

now you can assign data_lb, start_lb in your test bench side.

In reply to aming:

The best way to drive interface wires is to use clocking blocks.

1800-2012 14.3 Clocking block declaration

  • A clockvar whose clocking_direction is inout shall behave as if it were two clockvars, one input and one output, having the same name and the same clocking_signal.
  • Reading the value of such an inout clockvar shall be equivalent to reading the corresponding input clockvar.
  • Writing to such an inout clockvar shall be equivalent to writing to the corresponding output clockvar.

Below is an example that I used as a test case in my training class:

interface dut_mem_if #(WR_PARAM =1, RD_PARAM=1, MEM_DEPTH_PARAM=16) // 
	(input logic clk);
	parameter hold_time   =2;  // 3ns  
	parameter setup_time  =2;
	logic rd, wr, rst_n; 
	logic[31:0] address;
	logic hold;
	wire[31:0] data;  // <------------------- the bi-direct
	
	clocking driver_cb @ (posedge clk);
		default input #setup_time output #hold_time;
		output rst_n, rd, wr, address, kind_cp; 
		input hold;
		inout data;   // <------------------- the bi-direct 
	endclocking : driver_cb

	clocking mon_cb @ (posedge clk);
		output rst_n, rd, wr, address, kind_cp; 
		input hold;
		input data;
	endclocking : mon_cb

	modport drvr_if_mp (clocking driver_cb);
	modport mon_if_mp (clocking mon_cb);


endinterface : dut_mem_if 

class dut_mem_driver #(WR_PARAM =1, RD_PARAM=1, MEM_DEPTH_PARAM=16);
	string tID="DRIVER";
	virtual interface dut_mem_if #( WR_PARAM, RD_PARAM, MEM_DEPTH_PARAM) .drvr_if_mp  vif;
 // ...
	virtual task write_task(logic [31:0] data, address);
		vif.driver_cb.rst_n <= 1'b1;
		this.vif.driver_cb.wr   <= 1'b1;
		this.vif.driver_cb.rd   <= 1'b0; 
		this.vif.driver_cb.data <= data;
		this.vif.driver_cb.address <= address; 
		@ (this.vif.driver_cb);
		wait(this.vif.driver_cb.hold==1'b0);
		this.vif.driver_cb.wr <= 1'b0;
		this.vif.driver_cb.rd <= 1'b0; 
		this.vif.driver_cb.data   <= 'Z;
	endtask : write_task
endclass : dut_mem_driver

Ben Cohen http://systemverilog.us/

You might want to read my DVCon paper on connecting class based testbenches to RTL: The Missing Link: The Testbench to DUT Connection | Technical Paper | Verification Academy

as well as my blog explaining some of the fundamental differences between nets and variables: http://go.mentor.com/wire-vs-reg

i thank everyone for replies, that was very useful! i think, i’ll use the example with the clocking block: it’s easy to reuse such a method.
Dave_59, thanks for the links. it refreshed my memory, guess i need to read about SV concepts once more.