Indexed part-select referencing different width_expr values

I am trying to write synthesizable code which would allow me to index certain bits from a vector for further manipulation; to be more specific, a wide data vector would be defined as the input of a module:

parameter WIDTH = 4096
input [WIDTH - 1:0] i_dat

then, multiple “masks” are defined to be assigned with the desired bits:

logic [$clog2(WIDTH) - 1:0][WIDTH/2 - 1:0] dat_masks;

In order to make this module parameterizable, the mask extraction is defined as follows:

always_comb
  begin

     for(int i = 0; i < $clog2(WIDTH); i++)
       begin

          for(int j = (2**i), k = 0; j < $clog2(WIDTH); j = j + (2**(i + 1)), k = k + (2**i))
            begin

               dat_masks[i][k +: pwrs_of_2[i]] = i_dat[j +: pwrs_of_2[i]];

            end
       end

  end

As you can see in the snippet above, each mask requires a different width_expr to generate the desired pattern. Because width_expr must be constant, I had defined earlier in the code the following list of constant values:

localparam int pwrs_of_2[10] = '{32'd1, 32'd2, 32'd4, 32'd8, 32'd16, 32'd32, 32'd64, 32'd128, 32'd256, 32'd512};

This way, I was hoping that pwrs_of_2[i] could be interpreted as a constant value in the loop; that doesn’t seem to be the case however. My simulator tool indicates an error saying that “Range width must be constant expression”. My synthesis tool, on the other hand, doesn’t produce any error, but produces the wrong result: pwrs_of_2[i] is always 1.

Does anybody know why pwrs_of_2[i] is not considered a constant value? Any ideas on how to have these different width values referenced by the respective loop iteration?

By the way, to further comment on this: by no means I am a SystemVerilog expert, but, in my opinion, I found this concept of a constant width_expr a bit too restrictive as it is (at least based on my experience trying to get around it). What I mean by that is: yes, width_expr should be constant indeed, but it should have to be constatnt “in the scope in which it is being used”. In other words, one should be able to write the previous code like:

always_comb
  begin

     for(int i = 0; i < $clog2(WIDTH); i++)
       begin

          for(int j = (2**i), k = 0; j < $clog2(WIDTH); j = j + (2**(i + 1)), k = k + (2**i))
            begin

               dat_masks[i][k +: 2**i] = i_dat[j +: 2**i];

            end
       end

  end

The reason why I think so is because if one considers a for loop as simply an expression being unfolded into multiple “instances” of it, 2**i could be certainly seen as constant in each iteration of the outermost for loop.

Any feedback on that would be appreciated.

In reply to pablo.tomaz:

The problem is that SystemVerilog is used as both a Hardware Description Language and a Testbench Language (as well as a few other purposes). Loop-unrolling is something a synthesis tool does. But a simulation tool does not distinguish between what is supposed to be synthesized or not. So the rules for the language are based on how a programming language needs to implement the functionality without understanding what the code is trying to do.

A generate for-loop is the way you tell the compiler to unroll the loop. So you can write your code as

for(genvar I = 0; I < $clog2(WIDTH); I++) begin : outer_loop
always_comb
          for(int j = (2**i), k = 0; j < $clog2(WIDTH); j = j + (2**(i + 1)), k = k + (2**i))
            begin : inner_loop
               dat_masks[i][k +: 2**i] = i_dat[j +: 2**i];
            end : inner_loop
end : outer_loop

In reply to dave_59:

Excellent! And thank you for the explanation; it makes perfect sense!