Interface BFM and port direction

I’m trying to implement a BFM using an abstract class and a concrete interface. It was assumed that the interface would contain one set of signals for the write (for the driver) and read (for the monitor) methods that the class implements.
An abstract class and a concrete interface look like this:

package pkgAbsBFM;
	virtual class AbsPktBFM;
		pure virtual task write(bit [7:0] pkt []);
		pure virtual task read(output bit [7:0] pkt []);
	endclass
endpackage: pkgAbsBFM

interface someintfPktBFM #(parameter DATA_WIDTH = 8)(input bit rst, input bit clk);
	import pkgAbsBFM::*;

	bit sop, eop, val;
	bit [DATA_WIDTH-1:0][7:0] data;
	int pad;
	bit grant, ready;

	clocking cb_driver@(posedge clk);
		output sop, eop, val, data, pad, ready; 
		input grant;
	endclocking: cb_driver

	clocking cb_monitor@(posedge clk);
		input sop, eop, val, data, pad, ready; 
		output grant;
	endclocking: cb_monitor

	modport mp_driver (clocking cb_driver);
	modport mp_monitor (clocking cb_monitor);

	class PktBFM_someintf #(parameter DATA_WIDTH = DATA_WIDTH) extends AbsPktBFM;
		task write(bit [7:0] pkt []);

 			cb_driver.data <= ...
 			...
		endtask: write

		task read(output bit [7:0] pkt []);
 			... <= cb_monitor.data;
 			...
			end
		endtask: read

	endclass: PktBFM_someintf

	PktBFM_someintf BFM = new;
endinterface: someintfPktBFM

The top level looks like this:

module top;
        import pkgAbsBFM::*; 
	someintfPktBFM someintfPktDriver (rst, clk_125MHz);
	someintfPktBFM someintfPktMonitor (rst, clk_125MHz);
	
	AbsPktBFM BFM_driver, BFM_monitor;

	initial begin
		BFM_driver = someintfPktDriver.BFM;	
		BFM_driver.write(tx_pkt);
	end

	initial begin
		BFM_monitor = someintfPktMonitor.BFM;	
		BFM_monitor.read(rx_pkt);
	end


	dut dut (
		.iclk(clk_125MHz),
		.irst(rst),

		.isop(someintfPktDriver.sop),
		.ieop(someintfPktDriver.eop),
		.ival(someintfPktDriver.val),
		.idata(someintfPktDriver.data),
		.ipad(someintfPktDriver.pad),
		.grant(someintfPktDriver.grant),
		.ready(someintfPktDriver.ready),		

		.osop(someintfPktMonitor.sop),
		.oeop(someintfPktMonitor.eop),
		.oval(someintfPktMonitor.val),
		.odata(someintfPktMonitor.data),
		.opad(someintfPktMonitor.pad)
	);

endmodule

As a result, I got an error (despite the fact that I used one interface instance for the driver and the other for the monitor):

# ** Error (suppressible): (vsim-3839) Variable '/testbench/someintfPktMonitor/data', driven via a port connection, is multiply driven.

Do I understand correctly that I need to implement a separate abstract class and a separate concrete interface for both the driver and the monitor?
I wanted to avoid duplication of descriptions and implement methods within one unit. But I don’t quite understand how. I did not use the modport and clocking block, because, as I understood, they will not solve this problem. Or am I wrong?
Thanks.

In reply to DefaultName:

You haven’t shown the port definitions for your dut, but I’m assuming that data a bidirectional bus. You’ll need to declare data as a wire, not a variable.

Also, I recommend not using modports just for your testbench. They are mainly just to provide additional information for synthesis tools.