Using interface in testbench and for modules connection

I want to create an interface that can be used for modules connection (using modport) and for driving DUT from testbench (with clocking blocks). It seems to me that it should be trivial thing to do, but I could do it without warnings. I guess I don’t quite understand how to use clocking block within interface.

So far for interface I have:

`timescale 10ns/1ns

interface AXIS_if #(
    parameter               TDATA_WIDTH   = 32,
    parameter               TUSER_WIDTH   = 32
) (
    input logic             CLK
);

    logic                   TVALID;
    logic                   TREADY;
    logic                   TLAST;
    logic [TDATA_WIDTH-1:0] TDATA;
    logic [TUSER_WIDTH-1:0] TUSER;

    clocking m_cb @ (posedge CLK);
        input               TREADY;
        output              TVALID;
        output              TLAST;
        output              TDATA;
        output              TUSER;
    endclocking

    clocking s_cb @ (posedge CLK);
        output              TREADY;
        input               TVALID;
        input               TLAST;
        input               TDATA;
        input               TUSER;
    endclocking

    modport master (
        input               TREADY,
        output              TVALID,
        output              TLAST,
        output              TDATA,
        output              TUSER
    );

    modport slave (
        output              TREADY,
        input               TVALID,
        input               TLAST,
        input               TDATA,
        input               TUSER
    );

    modport master_drv (
        clocking            m_cb
    );

    modport slave_drv (
        clocking            s_cb
    );
    
endinterface

for testbench driver:

`timescale 10ns/1ns

package axis_pkg;

    class axis_m_driver #(parameter int TDATA_WIDTH = 8, parameter int TUSER_WIDTH = 8);

        virtual AXIS_if#(.TDATA_WIDTH(TDATA_WIDTH), .TUSER_WIDTH(TUSER_WIDTH)).master_drv axis_vif;

        function new(virtual AXIS_if#(.TDATA_WIDTH(TDATA_WIDTH), .TUSER_WIDTH(TUSER_WIDTH)) axis_vif);
            this.axis_vif = axis_vif;
        endfunction

        task send_pkt (ref logic [TDATA_WIDTH-1:0] data_buf[], ref logic [TUSER_WIDTH-1:0] user_buf[]);

            logic last = 1'b0;
            int buf_idx = 0;
            int buf_size = data_buf.size();

            while (1) begin

                if (buf_idx == buf_size) begin
                    axis_vif.m_cb.TVALID <= 1'b0;
                    break;
                end

                last = (buf_idx == buf_size - 1) ? 1'b1 : 1'b0;

                axis_vif.m_cb.TVALID <= 1'b1;
                axis_vif.m_cb.TLAST <= last;
                axis_vif.m_cb.TDATA <= data_buf[buf_idx];
                axis_vif.m_cb.TUSER <= user_buf[buf_idx];

                if (axis_vif.m_cb.TREADY == 1'b1) begin
                    buf_idx++;
                end

                @ (axis_vif.m_cb);
                
            end

        endtask

    endclass
    
endpackage

for testbench:

`timescale 10ns/1ns

module tb_top ();

    import axis_pkg::*;

    localparam CLK_PERIOD = 10;
    localparam CLK_AFTER_RST = 5;


    logic clk = 0;
    logic rst_n;

    AXIS_if #(
        .TDATA_WIDTH   (8),
        .TUSER_WIDTH   (8)
    ) axis_from_dut_if (
        .CLK    (clk)
    );

    AXIS_if #(
        .TDATA_WIDTH   (8),
        .TUSER_WIDTH   (8)
    ) axis_to_dut_if (
        .CLK    (clk)
    );

    // Instantiate the DUT
    dut dut_inst (
        //
        .CLK            (clk),   
        .RST_N          (rst_n),
        //
        .AXIS_S_PORT_IF (axis_to_dut_if),
        .AXIS_M_PORT_IF (axis_from_dut_if)
    );

    
    always #(CLK_PERIOD/2) clk=~clk;
    initial begin
        rst_n = 0;
        repeat (CLK_AFTER_RST) @(posedge clk);

        rst_n = 1;
    end

    assign axis_from_dut_if.TREADY = 1'b1;    

    initial begin
        // Testbench stimulus here
        automatic axis_m_driver #(.TDATA_WIDTH (8), .TUSER_WIDTH (8)) axis_to_dut_drv;
        
        automatic logic [7:0] tdata_array [];
        automatic logic [7:0] tuser_array [];

        axis_to_dut_drv = new(axis_to_dut_if);

        tdata_array = new[10];
        tuser_array = new[10];
        for (int i = 0; i < tdata_array.size(); i++) begin
            tdata_array[i] = 8'(i);
            tuser_array[i] = 8'(i + 10);
        end

        wait (rst_n == 1'b1);
        @(posedge clk);

        axis_to_dut_drv.send_pkt(tdata_array, tuser_array);

        repeat (20) @(posedge clk);
        $stop;
    end

endmodule

and for DUT:

`timescale 10ns/1ns

module dut (
    // Clock and reset
    input logic                CLK,
    input logic                RST_N,
    
    AXIS_if.slave               AXIS_S_PORT_IF,
    AXIS_if.master              AXIS_M_PORT_IF
);

    assign AXIS_S_PORT_IF.TREADY = !AXIS_M_PORT_IF.TVALID | AXIS_M_PORT_IF.TREADY;    

    always_ff @(posedge CLK) begin
        if (RST_N == 1'b0) begin
            AXIS_M_PORT_IF.TVALID <= 1'b0;
        end else begin
            if (AXIS_S_PORT_IF.TVALID == 1'b1 && AXIS_S_PORT_IF.TREADY == 1'b1) begin
                AXIS_M_PORT_IF.TVALID <= 1'b1;
            end else if (AXIS_M_PORT_IF.TREADY == 1'b1) begin
                AXIS_M_PORT_IF.TVALID <= 1'b0;
            end
        end
        begin
            if (AXIS_S_PORT_IF.TVALID == 1'b1 && AXIS_S_PORT_IF.TREADY == 1'b1) begin
                AXIS_M_PORT_IF.TDATA  <= AXIS_S_PORT_IF.TDATA;
                AXIS_M_PORT_IF.TUSER  <= AXIS_S_PORT_IF.TUSER;
                AXIS_M_PORT_IF.TLAST  <= AXIS_S_PORT_IF.TLAST;
            end
        end
    end

endmodule

It behaves as expected, but I get warnings:

# ** Warning: (vsim-3838) D:/PRJ/SBR/rtl/src/test/dut.sv(12): Variable '/tb_top/axis_to_dut_if/TREADY' written by continuous and procedural assignments. 
# One of the assignments is implicit. See D:/PRJ/SBR/FPGA_tools/tb_common/AXIS_if.sv(12).
#    Time: 0 ns  Iteration: 0  Instance: /tb_top/dut_inst File: D:/PRJ/SBR/rtl/src/test/dut.sv
# ** Warning: (vsim-3838) D:/PRJ/SBR/rtl/tbs/test_tb/tb_top.sv(47): Variable '/tb_top/axis_from_dut_if/TREADY' written by continuous and procedural assignments. 
# One of the assignments is implicit. See D:/PRJ/SBR/FPGA_tools/tb_common/AXIS_if.sv(12).
#    Time: 0 ns  Iteration: 0  Instance: /tb_top File: D:/PRJ/SBR/rtl/tbs/test_tb/tb_top.sv

As far as I understand procedural assignment in clocking block don’t want to work with continuous assignments in DUT, but I don’t know how to fix this problem. Am I misunderstanding something? Is it possible to get rid of this warnings?

Modports are design constructs while clocking blocks are verification constructs. Trying to combine the two into one interface can cause issues as you have discovered.

A clocking block creates an implicit driver on an output signal. In your case, TREADY is being driven by both the clocking block and the DUT, which is why you are getting a warning.

I recommend removing the clocking blocks as they aren’t necessary. Add logic to your interface that allows it to be used as either a master or slave when being used in a UVM environment.