verilogsystem-verilog

Is it possible to create task within interface for specific modport?


I want to create an interface for AXI-stream with tasks for testbench for sending and receiving data. As far as I understand I have some issues with task visibility in the interface.

My AXI-stream interface so far looks like this, tb_axis.sv:

`timescale 1ns/1ns

import tb_axis_pkg::*;

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

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

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

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

    task automatic get_packet();
        @ (posedge CLK);
        TREADY <= 1'b1;
        // write data to data_buffer object here
    endtask

    task automatic send(const ref data_buffer_c #(TDATA_WIDTH, TUSER_WIDTH) data_buffer);
        
        for (int i = 0; i < data_buffer.size(); ++i) begin

            @ (posedge CLK);

            TVALID <= 1'b1;
            TDATA <= data_buffer.axis_packet[i].tdata;
            TUSER <= data_buffer.axis_packet[i].tuser;
            TLAST <= (i == data_buffer.size() - 1) ? 1'b1 : 1'b0;

            while (TREADY == 1'b0)  @ (posedge CLK);

        end

        TVALID <= 1'b0;

    endtask

endinterface

I have slave and master modports for different directions of the interface, and tasks get_packet and send.

Usage of the interface in a testbench, test_tb.sv:

`timescale 1ns/1ns

import tb_axis_pkg::*;

module test_tb ();

    localparam int      CLK_PERIOD = 5;
    localparam int      WAIT_AFTER_RESET = 10;

    localparam int      TEST_QTY = 1;

    localparam int      TDATA_WIDTH = 16;
    localparam int      TUSER_WIDTH = 16;

    logic clk = 1'b0;
    logic reset = 1'b1;

    event new_iteration;

    AXIS #(
        .TDATA_WIDTH (TDATA_WIDTH),
        .TUSER_WIDTH (TUSER_WIDTH)
    )                       dut_in (clk);

    AXIS #(
        .TDATA_WIDTH (TDATA_WIDTH),
        .TUSER_WIDTH (TUSER_WIDTH)
    )                       dut_out (clk);
    
    test_module DUT (
        .clk    (clk),
        .reset  (reset),
        .AXIS_S (dut_in),
        .AXIS_M (dut_out)
    );

    initial begin
        forever begin
            #(CLK_PERIOD);
            clk <= ~clk;
        end
    end

    //
    initial begin
        
        reset = 1'b1;

        #(17);

        reset = 1'b0;

        repeat (WAIT_AFTER_RESET) @ (posedge clk);

        repeat (TEST_QTY) begin
            -> new_iteration;

            @ (posedge clk);
        end


    end

    // Slave
    initial begin

        data_buffer_c #(TDATA_WIDTH, TUSER_WIDTH)  data_buffer;

        forever begin

            wait (new_iteration.triggered);

            data_buffer = new();

            for (int i = 0; i < 10; ++i) begin
                data_buffer.push_back(i, 0);
            end
            
            dut_in.send(data_buffer);

        end
    end

    // Master
    initial begin
        forever begin

            wait (new_iteration.triggered);
            dut_out.get_packet();

            @ (posedge clk);
            
        end
    end

endmodule

to reproduce this example you also need

test_module.sv:

`timescale 1ns/1ns

module test_module (
    //
    input logic         clk,
    input logic         reset,
    //
    AXIS.slave          AXIS_S,
    AXIS.master         AXIS_M
);

    assign AXIS_S.TREADY = !AXIS_M.TVALID | AXIS_M.TREADY;

    always_ff @(clk) begin
        if (reset == 1'b1) begin
            AXIS_M.TVALID <= 1'b0;
            AXIS_M.TLAST <= 1'b0;
        end else begin

            if (AXIS_S.TVALID == 1'b1 && AXIS_S.TREADY == 1'b1) begin
                AXIS_M.TVALID <= 1'b1;
            end else if (AXIS_M.TREADY == 1'b1) begin
                AXIS_M.TVALID <= 1'b0;
            end

            if (AXIS_S.TVALID == 1'b1 && AXIS_S.TREADY == 1'b1) begin
                AXIS_M.TLAST <= AXIS_S.TLAST;
            end
            
        end
        begin
            if (AXIS_S.TVALID == 1'b1 && AXIS_S.TREADY == 1'b1) begin
                AXIS_M.TDATA <= AXIS_S.TDATA;
                AXIS_M.TUSER <= AXIS_S.TUSER;
            end
        end
    end
    
endmodule

and tb_axis_pkg.sv:

package tb_axis_pkg;

    class data_buffer_c #(int TDATA_WIDTH = 0, int TUSER_WIDTH = 0);

        typedef struct packed{
            logic [TDATA_WIDTH-1:0]                    tdata;
            logic [TUSER_WIDTH-1:0]                    tuser;
        } axis_word_t;

        axis_word_t axis_packet[$];

        //
        function automatic void push_back (input logic [TDATA_WIDTH-1:0] tdata, input logic[TUSER_WIDTH-1:0] tuser);
            
            axis_word_t new_word;

            new_word.tdata = tdata;
            new_word.tuser = tuser;

            this.axis_packet.push_back(new_word);

        endfunction

        //
        function automatic int size();

            return this.axis_packet.size();

        endfunction

    endclass
    
endpackage

During simulation I get an error:

# ** Error (suppressible): (vsim-12003) D:/Projects/SBR/Firmware/Fpga/source/test/test.sv(12): Variable '/test_tb/dut_in/TREADY' written by continuous and procedural assignments. See D:/Projects/SBR/Firmware/Fpga/source/library/tb_lib/tb_axis.sv(41).
#    Time: 0 ns  Iteration: 0  Instance: /test_tb/DUT File: D:/Projects/SBR/Firmware/Fpga/source/test/test.sv

As far as I understand, the reason for this error is the task get_packet in dut_in interface. So is there a way to specify task for modport, so get_packet was available only for dut_out?


Solution

  • I get a similar compile error on a different simulator (Cadence):

        assign AXIS_S.TREADY = !AXIS_M.TVALID | AXIS_M.TREADY;
                             |
    xmelab: *E,ICDPAV: Illegal combination of driver and procedural assignment to variable 
    TREADY detected (procedural assignment found in task/function get_packet )
    

    I don't think this error is related to a task. The error goes away when I change:

    assign AXIS_S.TREADY = !AXIS_M.TVALID | AXIS_M.TREADY;
    

    to:

    always_comb AXIS_S.TREADY = !AXIS_M.TVALID | AXIS_M.TREADY;