Intel® Quartus® Prime Software
Intel® Quartus® Prime Design Software, Design Entry, Synthesis, Simulation, Verification, Timing Analysis, System Design (Platform Designer, formerly Qsys)
17117 Discussions

SystemVerilog loops in functions are completely broken

k_dz
Novice
310 Views

While writing a ternary adder tree (code below and in attached project archive), I wrote a constant function (IEEE 1800-2023 section 13.4.3) that uses a loop to calculate parameter values for lower level instances based on the given parameter. The code worked well in Vivado and Verilator, but is totally broken in Quartus Prime Lite 24.1.

The first problem is the loop not terminating on return, causing elaboration errors:

Error (10106): Verilog HDL Loop error at ternary_adder_tree.sv(15): loop must terminate within 5000 iterations
Error (10903): Verilog HDL error at ternary_adder_tree.sv(44): failed to elaborate task or function "smaller_pow_3"
Error (10192): Verilog HDL Defparam Statement error at ternary_adder_tree.sv(44): value for parameter "MaxSubN" must be constant expression
Error (12153): Can't elaborate top-level user hierarchy

The loop should terminate during second iteration (N = 9):

  1. p = 1; next_p = 3; cond false
  2. p = 3; next_p = 9; cond true - return from function with value 3

If the while (1) loop is replaced with an empty-body for (commented out in code), the loop terminates, but the function somehow returns a value that is not constant and also causes the 10192 error:

Warning (10242): Verilog HDL Function Declaration warning at ternary_adder_tree.sv(24): variable "p" may have a Don't Care value because it may not be assigned a value in every possible path through the statements preceding its use
Error (10192): Verilog HDL Defparam Statement error at ternary_adder_tree.sv(44): value for parameter "MaxSubN" must be constant expression

The warning (10242) emitted before the error is a false positive, because initialisation in for-loop construct is not conditional and in fact is the first thing that happens. Moving the initialisation of p to its declaration gets rid of the warning, but the error remains.

Replacing the loop with multiple if (x <= X) return Y; lines works, but is not scalable and prone to errors. Are loops in functions for some known reason broken/forbidden or am I doing something wrong? I've already seen loop generate constructs breaking things in Quartus, but I hope this time manual copy&paste is not necessary.

 

// Uncomment the line below to use a version that works in Quartus
// `define NO_LOOP

module ternary_adder_tree #(
    parameter int WIDTH = 8,
    parameter int N = 9
) (
    input logic [WIDTH-1:0] inputs[N],
    output logic [WIDTH-1:0] sum
);
    function automatic int smaller_pow_3(input int x);
`ifndef NO_LOOP
        int p = 1;
        int next_p;
        while (1) begin
            next_p = p * 3;
            if (next_p >= x) return p;
            p = next_p;
        end

        // This doesn't work either, but is not "infinite"
        // int p;
        // for (p = 1; p*3 < x; p *= 3);
        // return p;
`else
        // Quartus-compatible version
        if (x <= 3) return 1;
        if (x <= 9) return 3;
        if (x <= 27) return 9;
        if (x <= 81) return 27;
        if (x <= 243) return 81;
        if (x <= 729) return 243;
        if (x <= 2187) return 729;
        if (x <= 6561) return 2187;
        if (x <= 19683) return 6561;
        if (x <= 59049) return 19683;
        return 'x;
`endif
    endfunction
    function automatic int min(input int a, input int b);
        return a <= b ? a : b;
    endfunction

    localparam int MaxSubN = smaller_pow_3(N);
    localparam int Sub1N = MaxSubN;
    localparam int Sub2N = min(MaxSubN, N - Sub1N);
    localparam int Sub3N = min(MaxSubN, N - Sub1N - Sub2N);

    // Quartus for some reason requires generate keyword (conditional generate constructs are not supported outside generate regions)
    generate
        if (N == 3) assign sum = inputs[0] + inputs[1] + inputs[2];
        if (N == 2) assign sum = inputs[0] + inputs[1];
        if (N == 1) assign sum = inputs[0];
        if (N > 3) begin : g_adder_subadders
            logic [WIDTH-1:0] s1;
            logic [WIDTH-1:0] s2;

            ternary_adder_tree #(.WIDTH(WIDTH), .N(Sub1N)) i_at3_1(.inputs(inputs[0:Sub1N-1]), .sum(s1));
            ternary_adder_tree #(.WIDTH(WIDTH), .N(Sub2N)) i_at3_2(.inputs(inputs[Sub1N:Sub1N+Sub2N-1]), .sum(s2));

            if (Sub3N > 0) begin : g_adder_sub_full3
                logic [WIDTH-1:0] s3;
                ternary_adder_tree #(.WIDTH(WIDTH), .N(Sub3N)) i_at3_3(.inputs(inputs[Sub1N+Sub2N:N-1]), .sum(s3));

                assign sum = s1 + s2 + s3;
            end else begin : g_adder_sub_only2
                assign sum = s1 + s2;
            end
        end
    endgenerate
endmodule

 

Labels (1)
0 Kudos
1 Solution
k_dz
Novice
228 Views

Working version

After trying multiple different ways, I found a working version:

function automatic int smaller_pow_3(input int x);
smaller_pow_3 = 1;
while(smaller_pow_3 * 3 < x) smaller_pow_3 *= 3;
endfunction

This has:

  • while instead of for
  • no local variables
  • no return statement (return value assigned to function name)

Not working versions

Other technically correct, but not working versions (based on the working one):

1. local variable + (optional) return

function automatic int smaller_pow_3(input int x);
int p = 1;
while (p * 3 < x) p *= 3;
return p;
// or
// smaller_pow_3 = p;
endfunction

Using a separate local variable makes Quartus think that the loop does not terminate. It does not matter whether a return statement or assignment to function name is used - just using a variable breaks synthesis.

2. Equivalent for loop

function automatic int smaller_pow_3(input int x);
for (smaller_pow_3 = 1; smaller_pow_3 * 3 < x; smaller_pow_3 *= 3)
;
endfunction

According to Verilog-2001 standard (IEEE 1364-2001, section 9.6) this should behave exactly like the working version with while loop, but in Quartus it warns that the function may return Don't Care (10241) and exits with an error, because the returned value is not a constant expression (10192).

Conclusions

I'm not sure why the not working versions don't, work, but if someone else has this problem (in SystemVerilog or Verilog) try the following.

In Quartus Prime Standard/Lite:

  • don't use for loops - try an equivalent while
  • don't use local variables in functions
  • if you need a variable to return, use assignment to function name (even as temporary variable)

View solution in original post

0 Kudos
4 Replies
RichardTanSY_Intel
291 Views

Unfortunately, SystemVerilog has limited support in Quartus Standard or Lite Edition, and the software has been on maintenance mode years ago and no feature is added.

To know more about the SystemVerilog construct supported in Quartus Standard/Lite (available in offline only), you will need to go to the Quartus > Help > Help Topics. (You will need to install the "Quartus Prime Help <version>" first before navigate to the "Quartus Prime Standard Edition Help version 18.1"). Then, search for Quartus Prime Support for SystemVerilog.

https://www.intel.com/content/www/us/en/support/programmable/articles/000097917.html


If you have the Quartus Prime Pro software, your code or project will compile successfully.


Regards,

Richard Tan


0 Kudos
FvM
Honored Contributor II
275 Views

Hi,

the problem isn't specific to SystemVerilog, Quartus synthesis simply doesn't like infinite loops. You get quite similar behaviour e.g. in VHDL.

The function parameters are however numbers of finite length, e.g. 32 bit integer. All you need to do is to rewrite your function in a forms that limits the iteration count to a finite value. To be recognized as a constant value, the function result also must not depend on previous iterations, as you already found out.

int i;
for (i=0; i < 10; i++)
   if (3**(i+1) >= x) return 3**i;
return 3**10;


Regards
Frank
 

0 Kudos
k_dz
Novice
229 Views

Working version

After trying multiple different ways, I found a working version:

function automatic int smaller_pow_3(input int x);
smaller_pow_3 = 1;
while(smaller_pow_3 * 3 < x) smaller_pow_3 *= 3;
endfunction

This has:

  • while instead of for
  • no local variables
  • no return statement (return value assigned to function name)

Not working versions

Other technically correct, but not working versions (based on the working one):

1. local variable + (optional) return

function automatic int smaller_pow_3(input int x);
int p = 1;
while (p * 3 < x) p *= 3;
return p;
// or
// smaller_pow_3 = p;
endfunction

Using a separate local variable makes Quartus think that the loop does not terminate. It does not matter whether a return statement or assignment to function name is used - just using a variable breaks synthesis.

2. Equivalent for loop

function automatic int smaller_pow_3(input int x);
for (smaller_pow_3 = 1; smaller_pow_3 * 3 < x; smaller_pow_3 *= 3)
;
endfunction

According to Verilog-2001 standard (IEEE 1364-2001, section 9.6) this should behave exactly like the working version with while loop, but in Quartus it warns that the function may return Don't Care (10241) and exits with an error, because the returned value is not a constant expression (10192).

Conclusions

I'm not sure why the not working versions don't, work, but if someone else has this problem (in SystemVerilog or Verilog) try the following.

In Quartus Prime Standard/Lite:

  • don't use for loops - try an equivalent while
  • don't use local variables in functions
  • if you need a variable to return, use assignment to function name (even as temporary variable)
0 Kudos
RichardTanSY_Intel
181 Views

Thank you for sharing the solution. This will help other users who might come across a similar issue.


Regards,

Richard Tan


0 Kudos
Reply