From 892279408881724d98ebf604f7225e5ee00c960a Mon Sep 17 00:00:00 2001 From: Leela Pakanati <41307800+cachanova@users.noreply.github.com> Date: Sat, 7 Feb 2026 09:06:37 -0600 Subject: [PATCH] Tests: Add test cases for interface array access with loop variable index (#1418 tests) (#7011) --- test_regress/t/t_interface_array_loop.py | 16 + test_regress/t/t_interface_array_loop.v | 371 ++++++++++++++++++ test_regress/t/t_interface_array_loop_bad.out | 10 + test_regress/t/t_interface_array_loop_bad.py | 16 + test_regress/t/t_interface_array_loop_bad.v | 36 ++ .../t/t_interface_array_loop_noinl.py | 17 + 6 files changed, 466 insertions(+) create mode 100755 test_regress/t/t_interface_array_loop.py create mode 100644 test_regress/t/t_interface_array_loop.v create mode 100644 test_regress/t/t_interface_array_loop_bad.out create mode 100644 test_regress/t/t_interface_array_loop_bad.py create mode 100644 test_regress/t/t_interface_array_loop_bad.v create mode 100755 test_regress/t/t_interface_array_loop_noinl.py diff --git a/test_regress/t/t_interface_array_loop.py b/test_regress/t/t_interface_array_loop.py new file mode 100755 index 000000000..d376ab1f0 --- /dev/null +++ b/test_regress/t/t_interface_array_loop.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Test interface array access with loop variables +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(fails=True) + +test.passes() diff --git a/test_regress/t/t_interface_array_loop.v b/test_regress/t/t_interface_array_loop.v new file mode 100644 index 000000000..7502d61dc --- /dev/null +++ b/test_regress/t/t_interface_array_loop.v @@ -0,0 +1,371 @@ +// DESCRIPTION: Verilator: Test interface array access with loop variables +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Leela Pakanati +// SPDX-License-Identifier: CC0-1.0 + +interface simple_if; + logic data; + logic [7:0] value; + + modport source (output data, output value); + modport sink (input data, input value); +endinterface + +// Test 6: Submodule with interface array as sink modport port +module sub_sink ( + simple_if.sink ifaces [3:0], + output logic [7:0] sum_out +); + always_comb begin + sum_out = 8'b0; + for (int i = 0; i < 4; i++) begin + sum_out = sum_out + ifaces[i].value; + end + end +endmodule + +// Test 7: Submodule with interface array as source modport port +module sub_source ( + simple_if.source ifaces [3:0] +); + always_comb begin + for (int i = 0; i < 4; i++) begin + ifaces[i].data = 1'b1; + ifaces[i].value = 8'(i + 100); + end + end +endmodule + +// Test 8: Submodule with interface array without modport +module sub_generic ( + simple_if ifaces [3:0] +); + always_comb begin + for (int i = 0; i < 4; i++) begin + ifaces[i].data = 1'b0; + ifaces[i].value = 8'(i + 50); + end + end +endmodule + +// Test 11: Parameterized submodule with interface array size from parameter +module l1_param_sub #( + parameter NUM_IFACES = 4 +) ( + simple_if.sink l0_ifaces [NUM_IFACES-1:0], + output logic [7:0] l0_sum +); + always_comb begin + l0_sum = 8'b0; + for (int i = 0; i < NUM_IFACES; i++) begin + l0_sum = l0_sum + l0_ifaces[i].value; + end + end +endmodule + +module t (/*AUTOARG*/ + // Inputs + clk +); + + input clk; + + localparam N = 4; + + simple_if ifaces [N-1:0] (); + + // Test 1: Simple for loop writing to interface array + always_comb begin + for (int i = 0; i < N; i++) begin + ifaces[i].data = '0; + end + end + + // Test 2: For loop reading from interface array + logic [N-1:0] read_data; + always_comb begin + for (int j = 0; j < N; j++) begin + read_data[j] = ifaces[j].data; + end + end + + // Test 3: For loop with expression in body + always_comb begin + for (int k = 0; k < N-1; k++) begin + // Use k to index, accessing different member + ifaces[k].value = 8'(k); + end + ifaces[N-1].value = 8'hFF; + end + + // Test 4: Descending loop with >= comparison + logic [N-1:0] desc_data; + always_comb begin + for (int p = N-1; p >= 0; p--) begin + desc_data[p] = ifaces[p].data; + end + end + + // Test 4b: Ascending loop with <= comparison + logic [N-1:0] lte_data; + always_comb begin + for (int m = 0; m <= N-1; m++) begin + lte_data[m] = ifaces[m].data; + end + end + + // Test 4c: Descending loop with > comparison + simple_if gt_ifaces [N-1:0] (); + always_comb begin + for (int n = N; n > 0; n--) begin + gt_ifaces[n-1].data = 1'b1; + gt_ifaces[n-1].value = 8'(n); + end + end + + // Test 5: Multiple interface arrays in same loop + simple_if other_ifaces [N-1:0] (); + always_comb begin + for (int q = 0; q < N; q++) begin + other_ifaces[q].data = ifaces[q].data; + other_ifaces[q].value = ifaces[q].value; + end + end + + // Test 6: Interface array as sink modport port (read from ports in loop) + logic [7:0] sink_sum; + sub_sink u_sub_sink (.ifaces(ifaces), .sum_out(sink_sum)); + + // Test 7: Interface array as source modport port (write to ports in loop) + simple_if source_ifaces [N-1:0] (); + sub_source u_sub_source (.ifaces(source_ifaces)); + + // Test 8: Interface array without modport (read/write in loop) + simple_if generic_ifaces [N-1:0] (); + sub_generic u_sub_generic (.ifaces(generic_ifaces)); + + // Test 9: Ascending loop with step of 2 + simple_if step2_ifaces [7:0] (); + always_comb begin + for (int i = 0; i < 8; i += 2) begin + step2_ifaces[i].data = 1'b1; + step2_ifaces[i].value = 8'(i); + end + for (int i = 1; i < 8; i += 2) begin + step2_ifaces[i].data = 1'b0; + step2_ifaces[i].value = 8'hAA; + end + end + + // Test 10: Descending loop with step of -2 + simple_if step2d_ifaces [7:0] (); + always_comb begin + for (int i = 6; i >= 0; i -= 2) begin + step2d_ifaces[i].data = 1'b1; + step2d_ifaces[i].value = 8'(i); + end + for (int i = 7; i >= 1; i -= 2) begin + step2d_ifaces[i].data = 1'b0; + step2d_ifaces[i].value = 8'hBB; + end + end + + // Test 11: Parameterized submodule with interface array size from parameter + // Override NUM_IFACES from default 4 to 6 to test that pre-unroll + // uses the overridden parameter value (not the default) + simple_if l0_param_ifaces [5:0] (); + always_comb begin + for (int i = 0; i < 6; i++) begin + l0_param_ifaces[i].data = 1'b1; + l0_param_ifaces[i].value = 8'(i * 10); + end + end + logic [7:0] l0_param_sum; + l1_param_sub #(.NUM_IFACES(6)) u_l1_param ( + .l0_ifaces(l0_param_ifaces), + .l0_sum(l0_param_sum) + ); + + // Test 12: Complex index expression (N-1-i) - reversed assignment + simple_if rev_ifaces [N-1:0] (); + always_comb begin + for (int i = 0; i < N; i++) begin + rev_ifaces[N-1-i].data = 1'b1; + rev_ifaces[N-1-i].value = 8'(i); + end + end + + // Test 13: Non-zero-based interface array range [2:5] + simple_if nzb_ifaces [2:5] (); + always_comb begin + for (int i = 2; i <= 5; i++) begin + nzb_ifaces[i].data = 1'b1; + nzb_ifaces[i].value = 8'(i); + end + end + + // Verification + int cycle; + initial cycle = 0; + + always @(posedge clk) begin + cycle <= cycle + 1; + + // Check that interfaces were properly accessed + if (cycle == 1) begin + // After initialization, data should be 0 + for (int i = 0; i < N; i++) begin + if (ifaces[i].data !== 1'b0) begin + $display("%%Error: ifaces[%0d].data should be 0, got %b", i, ifaces[i].data); + $stop; + end + end + + // Check values + for (int i = 0; i < N-1; i++) begin + if (ifaces[i].value !== 8'(i)) begin + $display("%%Error: ifaces[%0d].value should be %0d, got %0d", i, i, ifaces[i].value); + $stop; + end + end + if (ifaces[N-1].value !== 8'hFF) begin + $display("%%Error: ifaces[%0d].value should be 0xFF, got %0d", N-1, ifaces[N-1].value); + $stop; + end + + // Test 4b: Check lte_data (uses <= comparison) + for (int i = 0; i < N; i++) begin + if (lte_data[i] !== 1'b0) begin + $display("%%Error: lte_data[%0d] should be 0, got %b", i, lte_data[i]); + $stop; + end + end + + // Test 4c: Check gt_ifaces (uses > comparison, values are 1,2,3,4) + for (int i = 0; i < N; i++) begin + if (gt_ifaces[i].data !== 1'b1) begin + $display("%%Error: gt_ifaces[%0d].data should be 1, got %b", i, gt_ifaces[i].data); + $stop; + end + if (gt_ifaces[i].value !== 8'(i + 1)) begin + $display("%%Error: gt_ifaces[%0d].value should be %0d, got %0d", i, i+1, gt_ifaces[i].value); + $stop; + end + end + + // Test 6: Check sink_sum (sum of ifaces[0..3].value = 0+1+2+255 = 258 = 0x02) + if (sink_sum !== 8'h02) begin + $display("%%Error: sink_sum should be 0x02, got 0x%02x", sink_sum); + $stop; + end + + // Test 7: Check source_ifaces (written by sub_source with i+100) + for (int i = 0; i < N; i++) begin + if (source_ifaces[i].data !== 1'b1) begin + $display("%%Error: source_ifaces[%0d].data should be 1, got %b", i, source_ifaces[i].data); + $stop; + end + if (source_ifaces[i].value !== 8'(i + 100)) begin + $display("%%Error: source_ifaces[%0d].value should be %0d, got %0d", i, i+100, source_ifaces[i].value); + $stop; + end + end + + // Test 8: Check generic_ifaces (written by sub_generic with i+50) + for (int i = 0; i < N; i++) begin + if (generic_ifaces[i].data !== 1'b0) begin + $display("%%Error: generic_ifaces[%0d].data should be 0, got %b", i, generic_ifaces[i].data); + $stop; + end + if (generic_ifaces[i].value !== 8'(i + 50)) begin + $display("%%Error: generic_ifaces[%0d].value should be %0d, got %0d", i, i+50, generic_ifaces[i].value); + $stop; + end + end + + // Test 9: Check step2_ifaces (even indices have i, odd have 0xAA) + for (int i = 0; i < 8; i++) begin + if (i % 2 == 0) begin + if (step2_ifaces[i].data !== 1'b1) begin + $display("%%Error: step2_ifaces[%0d].data should be 1, got %b", i, step2_ifaces[i].data); + $stop; + end + if (step2_ifaces[i].value !== 8'(i)) begin + $display("%%Error: step2_ifaces[%0d].value should be %0d, got %0d", i, i, step2_ifaces[i].value); + $stop; + end + end else begin + if (step2_ifaces[i].data !== 1'b0) begin + $display("%%Error: step2_ifaces[%0d].data should be 0, got %b", i, step2_ifaces[i].data); + $stop; + end + if (step2_ifaces[i].value !== 8'hAA) begin + $display("%%Error: step2_ifaces[%0d].value should be 0xAA, got 0x%02x", i, step2_ifaces[i].value); + $stop; + end + end + end + + // Test 10: Check step2d_ifaces (even indices descending, odd indices 0xBB) + for (int i = 0; i < 8; i++) begin + if (i % 2 == 0) begin + if (step2d_ifaces[i].data !== 1'b1) begin + $display("%%Error: step2d_ifaces[%0d].data should be 1, got %b", i, step2d_ifaces[i].data); + $stop; + end + if (step2d_ifaces[i].value !== 8'(i)) begin + $display("%%Error: step2d_ifaces[%0d].value should be %0d, got %0d", i, i, step2d_ifaces[i].value); + $stop; + end + end else begin + if (step2d_ifaces[i].data !== 1'b0) begin + $display("%%Error: step2d_ifaces[%0d].data should be 0, got %b", i, step2d_ifaces[i].data); + $stop; + end + if (step2d_ifaces[i].value !== 8'hBB) begin + $display("%%Error: step2d_ifaces[%0d].value should be 0xBB, got 0x%02x", i, step2d_ifaces[i].value); + $stop; + end + end + end + + // Test 11: Check l0_param_sum (0*10 + 1*10 + 2*10 + 3*10 + 4*10 + 5*10 = 150) + if (l0_param_sum !== 8'd150) begin + $display("%%Error: l0_param_sum should be 150, got %0d", l0_param_sum); + $stop; + end + + // Test 12: Check rev_ifaces (reversed: rev_ifaces[N-1-i].value = i) + // So rev_ifaces[3].value=0, rev_ifaces[2].value=1, rev_ifaces[1].value=2, rev_ifaces[0].value=3 + for (int i = 0; i < N; i++) begin + if (rev_ifaces[i].data !== 1'b1) begin + $display("%%Error: rev_ifaces[%0d].data should be 1, got %b", i, rev_ifaces[i].data); + $stop; + end + if (rev_ifaces[i].value !== 8'(N - 1 - i)) begin + $display("%%Error: rev_ifaces[%0d].value should be %0d, got %0d", i, N-1-i, rev_ifaces[i].value); + $stop; + end + end + + // Test 13: Check nzb_ifaces (non-zero-based [2:5], nzb_ifaces[i].value = i) + for (int i = 2; i <= 5; i++) begin + if (nzb_ifaces[i].data !== 1'b1) begin + $display("%%Error: nzb_ifaces[%0d].data should be 1, got %b", i, nzb_ifaces[i].data); + $stop; + end + if (nzb_ifaces[i].value !== 8'(i)) begin + $display("%%Error: nzb_ifaces[%0d].value should be %0d, got %0d", i, i, nzb_ifaces[i].value); + $stop; + end + end + end + + if (cycle == 5) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_interface_array_loop_bad.out b/test_regress/t/t_interface_array_loop_bad.out new file mode 100644 index 000000000..a0b24d9c7 --- /dev/null +++ b/test_regress/t/t_interface_array_loop_bad.out @@ -0,0 +1,10 @@ +%Error: t/t_interface_array_loop_bad.v:27:14: Expecting expression to be constant, but variable isn't const: 'i' + : ... note: In instance 't' + 27 | ifaces[i].value = 8'(i); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_interface_array_loop_bad.v:27:13: Could not expand constant selection inside dotted reference: 'i' + : ... note: In instance 't' + 27 | ifaces[i].value = 8'(i); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_interface_array_loop_bad.py b/test_regress/t/t_interface_array_loop_bad.py new file mode 100644 index 000000000..1584aa3de --- /dev/null +++ b/test_regress/t/t_interface_array_loop_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Test non-constant interface array loop index error +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_interface_array_loop_bad.v b/test_regress/t/t_interface_array_loop_bad.v new file mode 100644 index 000000000..93fa257ea --- /dev/null +++ b/test_regress/t/t_interface_array_loop_bad.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Test non-constant interface array loop index error +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Leela Pakanati +// SPDX-License-Identifier: CC0-1.0 + +interface simple_bad_if; + logic [7:0] value; +endinterface + +module t (/*AUTOARG*/ + // Inputs + clk +); + + input clk; + + localparam N = 4; + + simple_bad_if ifaces [N-1:0] (); + + // BAD: Loop with runtime-variable bound - not unrollable, so the + // interface array index cannot be resolved to a constant. + logic [7:0] limit; + always_comb begin + for (int i = 0; i < limit; i++) begin + ifaces[i].value = 8'(i); + end + end + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_interface_array_loop_noinl.py b/test_regress/t/t_interface_array_loop_noinl.py new file mode 100755 index 000000000..bb8c4d193 --- /dev/null +++ b/test_regress/t/t_interface_array_loop_noinl.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Test interface array access with loop variables (no inline) +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t/t_interface_array_loop.v" + +test.compile(fails=True, v_flags2=["-fno-inline"]) + +test.passes()