Error during testbench to dut connection using asbtract class + interfaces

Hi All,

I’m trying to follow this paper by @dave_59
https://verificationacademy.com/topics/systemverilog/the-missing-link-the-testbench-to-dut-connection/
and experiment with it for a simple config interface and a dut connection.

My code is as follows,

package abstract_if_pkg;

virtual class abstract_ifc #(int cwidth=2, int awidth=8, int dwidth=8);
    pure virtual task write (input logic [awidth-1:0] addr, input logic [dwidth-1:0] data);
    pure virtual task read (input logic [awidth-1:0] addr );
    pure virtual task rsp ();
endclass

endpackage

package cfg_pkg;

import abstract_if_pkg::*;

class cfg_driver;

    string name  = "";
    //logic [7:0] mem[int];
    //virtual intf v_intf;
    abstract_ifc #(2,8,8) if_ah;

    function new (string name);
        this.name = name;
    endfunction
    
    //The following tasks in the driver which addresses if cbs directly will be written in if itself

    virtual task write (int addr, int data);
        if_ah.write(addr,data);
    endtask

    virtual task read(int addr);
        if_ah.read(addr);
    endtask

    virtual task rsp();
        if_ah.rsp();
    endtask

endclass 

endpackage

//interface def


interface intf #(parameter CMD_WIDTH=2,ADDR_WIDTH=8,DATA_WIDTH=8) (input clk );

    logic [31:0] cfg = '0;
    logic cfg_val = '0;
    logic [1:0] cmd;
    logic [7:0] addr;
    wire  [7:0] data;
    logic [7:0] mem[int];

    modport DUT (input cfg, cfg_val, output cmd, addr, inout data); 
    modport TB  (output cfg, cfg_val, input cmd, addr, inout data); 
    modport MON (input cfg, cfg_val, cmd, addr, data); 

    clocking drv @(posedge clk);
        default input #10ps output #20ps ;
        output cfg, cfg_val;
        input cmd, addr;
        inout data;
    endclocking

    clocking mon @(posedge clk);
        default input #10ps output #20ps ;
        input cfg, cfg_val;
        input cmd, addr;
        input data;
    endclocking

    import abstract_if_pkg::*;

    //creating the actual class
    class concrete_ifc #(int cwidth=2,int awidth=8,int dwidth=8) extends abstract_ifc #(2,8,8);
    //class concrete_ifc  extends abstract_ifc ;
    //class concrete_ifc #(int cwidth=2,int awidth=8,int dwidth=8) extends abstract_ifc ;
        task write (input logic [awidth-1:0] addr, input logic [dwidth-1:0] data);
            drv.cfg[31:24] <= addr;
            drv.cfg[23:16] <= data;
            drv.cfg[1:0]   <= 2;
            drv.cfg_val    <= 1;
            @(drv);
            drv.cfg_val    <= 0;
        endtask

        task read(input logic [awidth-1:0] addr);
            drv.cfg[31:24] <= addr;
            drv.cfg[1:0]   <= 1;
            drv.cfg_val    <= 1;
            @(drv);
            drv.cfg_val    <= 0;
        endtask

        task rsp();
        //While write put the address and data into mem
            forever begin
                @(drv);
                drv.data <= 'z; 
                if(drv.cmd == 2) begin
                    mem[drv.addr] = drv.data;
                    $display("updating the mem at addr %0x with %0x at time %0t",drv.addr,mem[drv.addr],$realtime);
                end
                if(drv.cmd == 1) begin
                    drv.data <= mem[drv.addr];
                    $display("driving out with data %0x at addr %0x at time %0t",drv.data,drv.addr,$realtime);
                end
            end
        endtask
    endclass

    concrete_ifc #(CMD_WIDTH,ADDR_WIDTH,DATA_WIDTH) if_ch = new();

    
endinterface

//dut def
module dut(

    input  logic clk,
    input  logic rst_n,
    intf.DUT dut_if 
);
//implementation 
endmodule
//tb_top def
module tb_top();

import cfg_pkg::*;

logic clk = 0;
logic rst_n = 1;

initial begin
    forever begin
        #5ns clk = ~clk;
    end
end

intf#(2,8,8)   i_intf1(clk);
intf#(2,16,16) i_intf2(clk);
intf#(3,32,32) i_intf3(clk);

cfg_driver drv;

initial begin
    //create the driver class
    drv = new("drv");
    drv.if_ah = i_intf1.if_ch;
    #10ns;
    //initiating the response
    fork
        drv.rsp();
    join_none
    rst_n = 0;
    #10ns;
    rst_n = 1;
    repeat(10)@ (i_intf1.drv);
    drv.write('haa,'h55);
    repeat(10)@ (i_intf1.drv);
    drv.write('hbb,'h66);
    repeat(10)@ (i_intf1.drv);
    drv.write('hcc,'h77);
    repeat(10)@ (i_intf1.drv);
    drv.read('haa);
    repeat(10)@ (i_intf1.drv);
    drv.write('hdd,'h99);
    repeat(10)@ (i_intf1.drv);
    drv.read('hbb);
    repeat(10)@ (i_intf1.drv);
    $finish();
end

dut dut_inst1 (clk, rst_n, i_intf1.DUT);
dut dut_inst2 (clk, rst_n, i_intf2.DUT);
dut dut_inst3 (clk, rst_n, i_intf3.DUT);

endmodule

Here I’m getting a compile error saying that,

“Virtual method ‘concrete_ifc::write/read’ formal argument type does not match base class ‘abstract_ifc’.”

I would like to understand if this is a problem with my tool or am I missing something here?

If I put the abstract and concrete tasks in a single package, I dont get the error. But then I cannot access interface methods when I do like that.

Any/All comments are appreciated. Thanks in advance!

The problem is with this line:

class concrete_ifc #(int cwidth=2,int awidth=8,int dwidth=8) extends abstract_ifc #(2,8,8);

You need pass the parameter values down to the base class.

  class concrete_ifc #(int cwidth,int awidth,int dwidth) extends abstract_ifc #(cwidth,awidth,dwidth);

I also suggest avoid giving parameters default values. This forces you to provide overridden values.

Hi @dave_59

Thanks a lot for the quick comment.

I started with not giving default param values for the abstract class,

virtual class abstract_ifc #(int cwidth, int awidth, int dwidth);
                                       |
: *E,SVVMAP (param_if.sv,5|39): Omitting the assignment to constant_param_expression in a param_assignment in a parameter_port_list currently is not supported.
virtual class abstract_ifc #(int cwidth, int awidth, int dwidth);

But ended up getting this error from my simulator. Could this be a tool limitation ?

Also the suggested fix also is not compiling,

    class concrete_ifc #(int cwidth,int awidth,int dwidth) extends abstract_ifc #(int cwidth,int awidth,int dwidth);
                                   |
: *E,SVVMAP (param_if.sv,80|35): Omitting the assignment to constant_param_expression in a param_assignment in a parameter_port_list currently is not supported.
    class concrete_ifc #(int cwidth,int awidth,int dwidth) extends abstract_ifc #(int cwidth,int awidth,int dwidth);

“currently is not supported.” is a tool issue. Add an absurd default value, like -1.

Hi @dave_59

Thanks, it worked after extending the following way,

class concrete_ifc #(int cwidth=-1,int awidth=-1,int dwidth=-1) extends abstract_ifc #(cwidth,awidth,dwidth);

Thanks a lot.

I have another doubt though, in the paper it is suggested that,

“Another benefit is that an abstract class can completely decouple a testbench class component from any dependencies on the SystemVerilog interface, such as parameters overrides”

Does that mean that in my example cfg_driver can be completely oblivious to parameter changes in the interface its driving?
While creating the handle of virtual class in the cfg_driver, we need to still mention the parameter right? So lets say I want to use the same cfg_driver for driving another interface instance with a different parameter set, how can I do that without changing the parameter values in cfg_driver class itself?

I was thinking of a scenario where cfg_driver is oblivious of parameter changes and tried to code something like this,

intf#(2,8,8)   i_intf1(clk);
intf#(2,16,16) i_intf2(clk);
intf#(3,32,32) i_intf3(clk);

cfg_driver drv,drv_2,drv_3;

initial begin
    //create the driver class
    drv = new("drv");
    drv.if_ah = i_intf1.if_ch;
    drv_2 = new("drv_2");
    drv_2.if_ah = i_intf2.if_ch;
    drv_3 = new("drv_3");
    drv_3.if_ah = i_intf3.if_ch;

The above code obviously wont work as there will be a type conflict in assignment.