Search code examples
system-verilogverificationuvm

Parametrized uvm sequence item to adjust size


I have an issue and I am out of ideas on how to resolve it. I have a class that contains an array called data. This dynamic array can be of parametrized packed width - either 8, 16, or 32 bits for example.

class MyItem#(int WIDTH=8) extends uvm_sequence_item;

  bit[WIDTH-1:0] data[];

endclass

The problem is, when I want to instantiate an object of MyItem, I need to provide it with the parameter. The WIDTH parameter however, depends on some other settings and for the puspose of the example lets assume can be randomly generated between 8,16, and 32.

class MySequence extends uvm_sequence;

  rand int width;
  constraint c_width {width inside {8, 16, 32}; }

  task body();
    MyItem#(width) req = MyItem#(width)::type_id::create("item"); // <--- THIS IS NOT ALLOWED
  endtask

endclass

So I had to come up with a way to work around this. The one way is to have a handle inside MyItem to all the different widths and then use the one I need. Something to the effect of:

class MyItem extends uvm_sequence_item;
  bit[ 7:0] data_08[];
  bit[15:0] data_16[];
  bit[23:0] data_24[];
endclass

This would allow me to use the handle depending on the packed size. But this is a bit hacky and I would prefer avoiding this as I have to keep checking the WIDTH parameter and access data_08, data_16 or data_24 seperately which bloats the code.

The other option is to just use a MAX_WIDTH parameter, and pad the bits.

class MyItem extends uvm_sequence_item;
  bit[MAX_WIDTH:0] data[];
endclass

But for very large arrays, this is a waste of memory and not efficient.

The final solution I came up with, but doesn't work is to create a base class called MyItemBase, and then Item extends from this. So now I can (or rather, should be able to) access data as just obj_handle.data

class MyItemBase extends uvm_sequence_item;
  bit data[];
endclass

class MyItem#(WIDTH) extends MyItemBase;
  bit[WIDTH-1:0] data[];
endclass

What this allows me to do is use the below code in my sequence:

  rand int width;
  constraint c_width {width inside {8, 16, 32}; }

  task body();
    MyItemBase req
    case(width)
      8:  req = MyItem#(8)::type_id::create("item");
      16: req = MyItem#(8)::type_id::create("item");
      32: req = MyItem#(8)::type_id::create("item");
      default: //
    endcase

    // Now I should be able to use req.data
    req.data = new[32];
    foreach(req.data[i]) req.data[i] = $urandom();
    foreach(req.data[i]) $display("data[%0d] = %0x",  i, data[i]);
  endtask

endclass

As you might have guessed, this didn't work. My data array is just one bit wide and I guess it is getting it from the base class, so polymorphism doesn't work for variables the way it does for functions (unless I am missing something key here).

Now having the single bit version of data in the MyItemBase class causes compilation errors unless I cast to the derived type. This causes some additional complexity and I feel I am getting way in over my head on a simple problem.

Can anyone give me any suggestions on how to structure this? Thanks


Solution

  • What I suggest is creating a 2-dimensional dynamic array. Since you seem to be working with bytes widths, you could do:

    bit[7:0] data[][];
    

    Then the second dimension can be constructed with

       foreach(data[I]) data[I]= new[width/8];
    

    If you need to work with a packed array, you could declare a local variable with a max width, and stream the data bytes from/to the local variable;

    bit [MAX_WIDTH-1:0] temp;
    ...
    temp = {<<{data[I]}};