fix: guard neuron_parallel against invalid N_INPUTS/PARALLEL combos

Both Phase 2 findings (docs/FPGA-NeuralNetwork-Engine.md) shared one
root cause: GROUPS = N_INPUTS / PARALLEL is integer division. When
N_INPUTS is not an exact multiple of PARALLEL, the remainder inputs
were silently dropped from the accumulation (wrong result, no
error); when PARALLEL > N_INPUTS, GROUPS = 0 and the controller's
terminal condition was never met, hanging the neuron forever.

Added a single elaboration-time guard to rtl/neuron_parallel.v: a
`generate` block instantiates a deliberately undefined module when
N_INPUTS % PARALLEL != 0, forcing a hard failure in both simulation
and synthesis instead of a silent wrong answer or a deadlock. Valid
configurations are unaffected (the branch is never elaborated). The
validated datapath (mac8/mac_unit/accumulation/ReLU/saturation) is
untouched -- this is authorized as a scoped exception to the
"core is fixed, do not touch" project policy, for this guard only.

- sim/neuron_parallel_guard_negative_nonmultiple_tb.v and
  sim/neuron_parallel_guard_negative_degenerate_tb.v: negative tests
  that must fail to elaborate; verified both fail with the expected
  "Unknown module type" error.
- sim/parameter_sweep_tb.v: rewritten to valid-configs-only (the
  three configs that used to demonstrate truncation/hang no longer
  compile, by design); added PARALLEL=2 and PARALLEL=4 configs,
  the two best-performing values from
  docs/FPGA-Neural-Datapatch-Benchmark.md.
- Full regression re-run after the RTL change: all existing
  testbenches still pass unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt
This commit is contained in:
2026-09-02 14:41:49 +02:00
co-authored by Claude Sonnet 5
parent 9b9859a104
commit 1a6f0ba2ef
14 changed files with 45001 additions and 62768 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
$date
Wed Sep 2 14:15:53 2026
Wed Sep 2 14:41:17 2026
$end
$version
Icarus Verilog
+4658 -4658
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
$date
Wed Sep 2 14:06:08 2026
Wed Sep 2 14:41:21 2026
$end
$version
Icarus Verilog
+25496 -25496
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,5 +1,5 @@
$date
Wed Sep 2 14:15:53 2026
Wed Sep 2 14:41:17 2026
$end
$version
Icarus Verilog
@@ -0,0 +1,41 @@
`timescale 1ns/1ps
// ================================================================
// NEGATIVE TEST - intentionally invalid parameter combination.
//
// This file must FAIL TO COMPILE/ELABORATE. That failure is the
// test: it proves the PARAMETER_ERROR_N_INPUTS_NOT_MULTIPLE_OF_PARALLEL
// guard in rtl/neuron_parallel.v rejects the degenerate case
// PARALLEL > N_INPUTS (Phase 2 finding: N_INPUTS=4, PARALLEL=8 used
// to leave GROUPS=0, causing the controller to hang forever instead
// of erroring).
//
// Verify with:
// iverilog -g2012 -o /tmp/out rtl/*.v \
// sim/neuron_parallel_guard_negative_degenerate_tb.v
//
// Expected: nonzero exit status and
// "error: Unknown module type:
// neuron_parallel_requires_N_INPUTS_multiple_of_PARALLEL"
// ================================================================
module tb;
neuron_parallel #(
.DATA_WIDTH(8),
.N_INPUTS(4),
.PARALLEL(8),
.ACC_WIDTH(32)
) u_invalid (
.clk(1'b0),
.rst(1'b0),
.start(1'b0),
.x_bus(32'b0),
.w_bus(32'b0),
.bias(8'sd0),
.y(),
.busy(),
.done()
);
endmodule
@@ -0,0 +1,41 @@
`timescale 1ns/1ps
// ================================================================
// NEGATIVE TEST - intentionally invalid parameter combination.
//
// This file must FAIL TO COMPILE/ELABORATE. That failure is the
// test: it proves the PARAMETER_ERROR_N_INPUTS_NOT_MULTIPLE_OF_PARALLEL
// guard in rtl/neuron_parallel.v rejects configurations where
// N_INPUTS is not an exact multiple of PARALLEL (Phase 2 finding:
// N_INPUTS=30, PARALLEL=8 used to silently drop the last 6 inputs
// instead of erroring).
//
// Verify with:
// iverilog -g2012 -o /tmp/out rtl/*.v \
// sim/neuron_parallel_guard_negative_nonmultiple_tb.v
//
// Expected: nonzero exit status and
// "error: Unknown module type:
// neuron_parallel_requires_N_INPUTS_multiple_of_PARALLEL"
// ================================================================
module tb;
neuron_parallel #(
.DATA_WIDTH(8),
.N_INPUTS(30),
.PARALLEL(8),
.ACC_WIDTH(32)
) u_invalid (
.clk(1'b0),
.rst(1'b0),
.start(1'b0),
.x_bus(240'b0),
.w_bus(240'b0),
.bias(8'sd0),
.y(),
.busy(),
.done()
);
endmodule
+5069 -5069
View File
File diff suppressed because one or more lines are too long
+2250 -18345
View File
File diff suppressed because it is too large Load Diff
+7216 -8918
View File
File diff suppressed because one or more lines are too long
+134 -262
View File
@@ -4,19 +4,30 @@
// PHASE 2 - PARAMETER SWEEP
//
// Roadmap requirement (docs/FPGA-NeuralNetwork-Engine.md, Phase 2):
// validate multiple combinations of N_INPUTS / N_NEURONS / PARALLEL,
// including configurations where N_INPUTS is NOT an exact multiple
// of PARALLEL.
// validate multiple combinations of N_INPUTS / N_NEURONS / PARALLEL.
//
// neuron_parallel.v computes:
// localparam GROUPS = N_INPUTS / PARALLEL;
// which is an INTEGER division. When N_INPUTS is not an exact
// multiple of PARALLEL, the remainder inputs are silently never
// read by the accumulator (GEN_TREE only ever selects the first
// GROUPS*PARALLEL inputs). This bench characterizes that behavior
// instead of assuming it does not exist, and uses a cycle-count
// watchdog (never a blocking `wait`) so a config that never
// asserts `done` is reported instead of hanging the simulation.
// HISTORY:
// The original version of this bench included non-exact-multiple
// configs (N_INPUTS=30/PARALLEL=8, N_INPUTS=20/PARALLEL=16) and a
// degenerate PARALLEL>N_INPUTS config (N_INPUTS=4/PARALLEL=8). Those
// exposed two silent-failure modes in rtl/neuron_parallel.v:
// - non-exact multiples: remainder inputs silently dropped (wrong
// result, no error).
// - PARALLEL > N_INPUTS: GROUPS=0, controller never asserts done
// (permanent hang).
// Both are now rejected at elaboration time by the
// PARAMETER_ERROR_N_INPUTS_NOT_MULTIPLE_OF_PARALLEL guard added to
// rtl/neuron_parallel.v, so those three configs would no longer
// compile -- which is the intended fix. Their negative-test coverage
// (proving the guard actually fires) lives in:
// sim/neuron_parallel_guard_negative_nonmultiple_tb.v
// sim/neuron_parallel_guard_negative_degenerate_tb.v
//
// This bench now sweeps only VALID (exact-multiple) configurations,
// including PARALLEL=2 and PARALLEL=4 -- the two best-performing
// parallelism values found in docs/FPGA-Neural-Datapatch-Benchmark.md
// (PARALLEL=2 is the only tested config that meets 80 MHz; PARALLEL=4
// is a close second).
// ================================================================
module tb;
@@ -30,7 +41,6 @@ module tb;
end
integer errors;
integer findings;
// ============================================================
// CONFIG A - baseline, exact multiple (sanity check)
@@ -60,62 +70,6 @@ module tb;
.y(y_a), .busy(busy_a), .done(done_a)
);
// ============================================================
// CONFIG B - non-exact multiple
// N_INPUTS=30 PARALLEL=8 -> GROUPS=3 (24 inputs actually summed)
// ============================================================
localparam B_DATA_WIDTH = 8;
localparam B_N_INPUTS = 30;
localparam B_PARALLEL = 8;
localparam B_ACC_WIDTH = 32;
reg start_b;
reg signed [B_DATA_WIDTH*B_N_INPUTS-1:0] x_bus_b;
reg signed [B_DATA_WIDTH*B_N_INPUTS-1:0] w_bus_b;
reg signed [B_DATA_WIDTH-1:0] bias_b;
wire signed [B_DATA_WIDTH-1:0] y_b;
wire busy_b, done_b;
neuron_parallel #(
.DATA_WIDTH(B_DATA_WIDTH),
.N_INPUTS(B_N_INPUTS),
.PARALLEL(B_PARALLEL),
.ACC_WIDTH(B_ACC_WIDTH)
) u_b (
.clk(clk), .rst(rst), .start(start_b),
.x_bus(x_bus_b), .w_bus(w_bus_b), .bias(bias_b),
.y(y_b), .busy(busy_b), .done(done_b)
);
// ============================================================
// CONFIG C - non-exact multiple, different PARALLEL
// N_INPUTS=20 PARALLEL=16 -> GROUPS=1 (16 inputs actually summed)
// ============================================================
localparam C_DATA_WIDTH = 8;
localparam C_N_INPUTS = 20;
localparam C_PARALLEL = 16;
localparam C_ACC_WIDTH = 32;
reg start_c;
reg signed [C_DATA_WIDTH*C_N_INPUTS-1:0] x_bus_c;
reg signed [C_DATA_WIDTH*C_N_INPUTS-1:0] w_bus_c;
reg signed [C_DATA_WIDTH-1:0] bias_c;
wire signed [C_DATA_WIDTH-1:0] y_c;
wire busy_c, done_c;
neuron_parallel #(
.DATA_WIDTH(C_DATA_WIDTH),
.N_INPUTS(C_N_INPUTS),
.PARALLEL(C_PARALLEL),
.ACC_WIDTH(C_ACC_WIDTH)
) u_c (
.clk(clk), .rst(rst), .start(start_c),
.x_bus(x_bus_c), .w_bus(w_bus_c), .bias(bias_c),
.y(y_c), .busy(busy_c), .done(done_c)
);
// ============================================================
// CONFIG D - exact multiple, wide parallelism (sanity check)
// N_INPUTS=64 PARALLEL=32 -> GROUPS=2
@@ -145,42 +99,70 @@ module tb;
);
// ============================================================
// CONFIG E - degenerate: PARALLEL > N_INPUTS
// N_INPUTS=4 PARALLEL=8 -> GROUPS=0
// Watchdog-guarded: expected to NOT complete (documents the
// constraint "PARALLEL must not exceed N_INPUTS").
// CONFIG F - PARALLEL=2 (best timing per benchmark)
// N_INPUTS=32 PARALLEL=2 -> GROUPS=16
// ============================================================
localparam E_DATA_WIDTH = 8;
localparam E_N_INPUTS = 4;
localparam E_PARALLEL = 8;
localparam E_ACC_WIDTH = 32;
localparam F_DATA_WIDTH = 8;
localparam F_N_INPUTS = 32;
localparam F_PARALLEL = 2;
localparam F_ACC_WIDTH = 32;
reg start_e;
reg signed [E_DATA_WIDTH*E_N_INPUTS-1:0] x_bus_e;
reg signed [E_DATA_WIDTH*E_N_INPUTS-1:0] w_bus_e;
reg signed [E_DATA_WIDTH-1:0] bias_e;
wire signed [E_DATA_WIDTH-1:0] y_e;
wire busy_e, done_e;
reg start_f;
reg signed [F_DATA_WIDTH*F_N_INPUTS-1:0] x_bus_f;
reg signed [F_DATA_WIDTH*F_N_INPUTS-1:0] w_bus_f;
reg signed [F_DATA_WIDTH-1:0] bias_f;
wire signed [F_DATA_WIDTH-1:0] y_f;
wire busy_f, done_f;
neuron_parallel #(
.DATA_WIDTH(E_DATA_WIDTH),
.N_INPUTS(E_N_INPUTS),
.PARALLEL(E_PARALLEL),
.ACC_WIDTH(E_ACC_WIDTH)
) u_e (
.clk(clk), .rst(rst), .start(start_e),
.x_bus(x_bus_e), .w_bus(w_bus_e), .bias(bias_e),
.y(y_e), .busy(busy_e), .done(done_e)
.DATA_WIDTH(F_DATA_WIDTH),
.N_INPUTS(F_N_INPUTS),
.PARALLEL(F_PARALLEL),
.ACC_WIDTH(F_ACC_WIDTH)
) u_f (
.clk(clk), .rst(rst), .start(start_f),
.x_bus(x_bus_f), .w_bus(w_bus_f), .bias(bias_f),
.y(y_f), .busy(busy_f), .done(done_f)
);
// ============================================================
// CONFIG G - PARALLEL=4 (close second per benchmark)
// N_INPUTS=32 PARALLEL=4 -> GROUPS=8
// ============================================================
localparam G_DATA_WIDTH = 8;
localparam G_N_INPUTS = 32;
localparam G_PARALLEL = 4;
localparam G_ACC_WIDTH = 32;
reg start_g;
reg signed [G_DATA_WIDTH*G_N_INPUTS-1:0] x_bus_g;
reg signed [G_DATA_WIDTH*G_N_INPUTS-1:0] w_bus_g;
reg signed [G_DATA_WIDTH-1:0] bias_g;
wire signed [G_DATA_WIDTH-1:0] y_g;
wire busy_g, done_g;
neuron_parallel #(
.DATA_WIDTH(G_DATA_WIDTH),
.N_INPUTS(G_N_INPUTS),
.PARALLEL(G_PARALLEL),
.ACC_WIDTH(G_ACC_WIDTH)
) u_g (
.clk(clk), .rst(rst), .start(start_g),
.x_bus(x_bus_g), .w_bus(w_bus_g), .bias(bias_g),
.y(y_g), .busy(busy_g), .done(done_g)
);
// ============================================================
// MAIN
//
// Every config here is a VALID (exact-multiple) parameter
// combination, so a plain blocking `wait(done)` is safe -- the
// elaboration guard already rejects anything that could hang.
// ============================================================
integer max_cycles;
integer count;
reg timed_out;
initial begin
@@ -189,26 +171,22 @@ module tb;
rst = 1;
errors = 0;
findings = 0;
max_cycles = 500;
start_a = 0; x_bus_a = 0; w_bus_a = 0; bias_a = 0;
start_b = 0; x_bus_b = 0; w_bus_b = 0; bias_b = 0;
start_c = 0; x_bus_c = 0; w_bus_c = 0; bias_c = 0;
start_d = 0; x_bus_d = 0; w_bus_d = 0; bias_d = 0;
start_e = 0; x_bus_e = 0; w_bus_e = 0; bias_e = 0;
start_f = 0; x_bus_f = 0; w_bus_f = 0; bias_f = 0;
start_g = 0; x_bus_g = 0; w_bus_g = 0; bias_g = 0;
repeat (2) @(posedge clk);
rst = 0;
$display("");
$display("========================================");
$display("PHASE 2 - PARAMETER SWEEP");
$display("PHASE 2 - PARAMETER SWEEP (guarded, valid configs only)");
$display("========================================");
// --------------------------------------------------------
// CONFIG A: all x=1, all w=1, bias=0
// full sum = 32, exact multiple -> expect 32
// CONFIG A: all x=1, all w=1, bias=0 -> expect 32
// --------------------------------------------------------
for (count = 0; count < A_N_INPUTS; count = count + 1) begin
x_bus_a[count*A_DATA_WIDTH +: A_DATA_WIDTH] = 8'sd1;
@@ -218,128 +196,22 @@ module tb;
@(posedge clk); start_a <= 1'b1;
@(posedge clk); start_a <= 1'b0;
timed_out = 1'b0;
count = 0;
while (!done_a && !timed_out) begin
@(posedge clk);
count = count + 1;
if (count > max_cycles) timed_out = 1'b1;
end
wait (done_a);
@(posedge clk);
$display("");
$display("CONFIG A: N_INPUTS=%0d PARALLEL=%0d (exact, GROUPS=%0d)",
$display("CONFIG A: N_INPUTS=%0d PARALLEL=%0d (GROUPS=%0d)",
A_N_INPUTS, A_PARALLEL, A_N_INPUTS/A_PARALLEL);
if (timed_out) begin
$display(" RESULT: TIMEOUT (did not assert done within %0d cycles)", max_cycles);
$display(" y = %0d expected = 32", y_a);
if (y_a !== 8'sd32) begin
$display(" FAIL");
errors = errors + 1;
end else begin
$display(" y = %0d expected = 32", y_a);
if (y_a !== 8'sd32) begin
$display(" FAIL");
errors = errors + 1;
end else begin
$display(" PASS");
end
$display(" PASS");
end
// --------------------------------------------------------
// CONFIG B: all x=1, all w=1, bias=0
// N_INPUTS=30, PARALLEL=8 -> GROUPS=3 -> only first 24
// inputs are actually summed by the current RTL.
// full-sum expectation would be 30; RTL-truncated
// expectation is 24. We check against the RTL-truncated
// value and flag the mismatch vs. the full sum as a
// documented finding (not a failure of this bench).
// --------------------------------------------------------
for (count = 0; count < B_N_INPUTS; count = count + 1) begin
x_bus_b[count*B_DATA_WIDTH +: B_DATA_WIDTH] = 8'sd1;
w_bus_b[count*B_DATA_WIDTH +: B_DATA_WIDTH] = 8'sd1;
end
bias_b = 0;
@(posedge clk); start_b <= 1'b1;
@(posedge clk); start_b <= 1'b0;
timed_out = 1'b0;
count = 0;
while (!done_b && !timed_out) begin
@(posedge clk);
count = count + 1;
if (count > max_cycles) timed_out = 1'b1;
end
@(posedge clk);
$display("");
$display("CONFIG B: N_INPUTS=%0d PARALLEL=%0d (NON-exact, GROUPS=%0d, %0d inputs actually read)",
B_N_INPUTS, B_PARALLEL, B_N_INPUTS/B_PARALLEL,
(B_N_INPUTS/B_PARALLEL)*B_PARALLEL);
if (timed_out) begin
$display(" RESULT: TIMEOUT (did not assert done within %0d cycles)", max_cycles);
errors = errors + 1;
end else begin
$display(" y = %0d RTL-truncated expected = 24 full-sum (NOT met) = 30", y_b);
if (y_b !== 8'sd24) begin
$display(" FAIL (unexpected value for current RTL behavior)");
errors = errors + 1;
end else begin
$display(" PASS (matches current RTL truncation behavior)");
end
if (y_b !== B_N_INPUTS[7:0]) begin
$display(" FINDING: last %0d input(s) are silently ignored (GROUPS = N_INPUTS/PARALLEL truncates)",
B_N_INPUTS - (B_N_INPUTS/B_PARALLEL)*B_PARALLEL);
findings = findings + 1;
end
end
// --------------------------------------------------------
// CONFIG C: same characterization, different sizes
// N_INPUTS=20, PARALLEL=16 -> GROUPS=1 -> only first 16 read
// --------------------------------------------------------
for (count = 0; count < C_N_INPUTS; count = count + 1) begin
x_bus_c[count*C_DATA_WIDTH +: C_DATA_WIDTH] = 8'sd1;
w_bus_c[count*C_DATA_WIDTH +: C_DATA_WIDTH] = 8'sd1;
end
bias_c = 0;
@(posedge clk); start_c <= 1'b1;
@(posedge clk); start_c <= 1'b0;
timed_out = 1'b0;
count = 0;
while (!done_c && !timed_out) begin
@(posedge clk);
count = count + 1;
if (count > max_cycles) timed_out = 1'b1;
end
@(posedge clk);
$display("");
$display("CONFIG C: N_INPUTS=%0d PARALLEL=%0d (NON-exact, GROUPS=%0d, %0d inputs actually read)",
C_N_INPUTS, C_PARALLEL, C_N_INPUTS/C_PARALLEL,
(C_N_INPUTS/C_PARALLEL)*C_PARALLEL);
if (timed_out) begin
$display(" RESULT: TIMEOUT (did not assert done within %0d cycles)", max_cycles);
errors = errors + 1;
end else begin
$display(" y = %0d RTL-truncated expected = 16 full-sum (NOT met) = 20", y_c);
if (y_c !== 8'sd16) begin
$display(" FAIL (unexpected value for current RTL behavior)");
errors = errors + 1;
end else begin
$display(" PASS (matches current RTL truncation behavior)");
end
if (y_c !== C_N_INPUTS[7:0]) begin
$display(" FINDING: last %0d input(s) are silently ignored (GROUPS = N_INPUTS/PARALLEL truncates)",
C_N_INPUTS - (C_N_INPUTS/C_PARALLEL)*C_PARALLEL);
findings = findings + 1;
end
end
// --------------------------------------------------------
// CONFIG D: all x=1, all w=1, bias=0
// full sum = 64, exact multiple -> expect 64
// CONFIG D: all x=1, all w=1, bias=0 -> expect 64
// --------------------------------------------------------
for (count = 0; count < D_N_INPUTS; count = count + 1) begin
x_bus_d[count*D_DATA_WIDTH +: D_DATA_WIDTH] = 8'sd1;
@@ -349,76 +221,76 @@ module tb;
@(posedge clk); start_d <= 1'b1;
@(posedge clk); start_d <= 1'b0;
timed_out = 1'b0;
count = 0;
while (!done_d && !timed_out) begin
@(posedge clk);
count = count + 1;
if (count > max_cycles) timed_out = 1'b1;
end
wait (done_d);
@(posedge clk);
$display("");
$display("CONFIG D: N_INPUTS=%0d PARALLEL=%0d (exact, GROUPS=%0d)",
$display("CONFIG D: N_INPUTS=%0d PARALLEL=%0d (GROUPS=%0d)",
D_N_INPUTS, D_PARALLEL, D_N_INPUTS/D_PARALLEL);
if (timed_out) begin
$display(" RESULT: TIMEOUT (did not assert done within %0d cycles)", max_cycles);
$display(" y = %0d expected = 64", y_d);
if (y_d !== 8'sd64) begin
$display(" FAIL");
errors = errors + 1;
end else begin
$display(" y = %0d expected = 64", y_d);
if (y_d !== 8'sd64) begin
$display(" FAIL");
errors = errors + 1;
end else begin
$display(" PASS");
end
$display(" PASS");
end
// --------------------------------------------------------
// CONFIG E: degenerate PARALLEL > N_INPUTS -> GROUPS=0
// We EXPECT this to time out. If it ever completes, that
// is itself worth flagging (behavior changed).
// CONFIG F: PARALLEL=2, all x=1, all w=1, bias=0 -> expect 32
// --------------------------------------------------------
for (count = 0; count < E_N_INPUTS; count = count + 1) begin
x_bus_e[count*E_DATA_WIDTH +: E_DATA_WIDTH] = 8'sd1;
w_bus_e[count*E_DATA_WIDTH +: E_DATA_WIDTH] = 8'sd1;
for (count = 0; count < F_N_INPUTS; count = count + 1) begin
x_bus_f[count*F_DATA_WIDTH +: F_DATA_WIDTH] = 8'sd1;
w_bus_f[count*F_DATA_WIDTH +: F_DATA_WIDTH] = 8'sd1;
end
bias_e = 0;
bias_f = 0;
@(posedge clk); start_e <= 1'b1;
@(posedge clk); start_e <= 1'b0;
timed_out = 1'b0;
count = 0;
while (!done_e && !timed_out) begin
@(posedge clk);
count = count + 1;
if (count > max_cycles) timed_out = 1'b1;
end
@(posedge clk); start_f <= 1'b1;
@(posedge clk); start_f <= 1'b0;
wait (done_f);
@(posedge clk);
$display("");
$display("CONFIG E: N_INPUTS=%0d PARALLEL=%0d (DEGENERATE, GROUPS=%0d)",
E_N_INPUTS, E_PARALLEL, E_N_INPUTS/E_PARALLEL);
if (timed_out) begin
$display(" RESULT: TIMEOUT as expected (done never asserted within %0d cycles)", max_cycles);
$display(" FINDING: PARALLEL > N_INPUTS (GROUPS=0) hangs neuron_parallel forever -- design constraint, not currently guarded in RTL");
findings = findings + 1;
end else begin
$display(" RESULT: completed with y=%0d (unexpected -- previously assumed to hang)", y_e);
$display("CONFIG F: N_INPUTS=%0d PARALLEL=%0d (GROUPS=%0d) -- best timing per benchmark",
F_N_INPUTS, F_PARALLEL, F_N_INPUTS/F_PARALLEL);
$display(" y = %0d expected = 32", y_f);
if (y_f !== 8'sd32) begin
$display(" FAIL");
errors = errors + 1;
end else begin
$display(" PASS");
end
// --------------------------------------------------------
// CONFIG G: PARALLEL=4, all x=1, all w=1, bias=0 -> expect 32
// --------------------------------------------------------
for (count = 0; count < G_N_INPUTS; count = count + 1) begin
x_bus_g[count*G_DATA_WIDTH +: G_DATA_WIDTH] = 8'sd1;
w_bus_g[count*G_DATA_WIDTH +: G_DATA_WIDTH] = 8'sd1;
end
bias_g = 0;
@(posedge clk); start_g <= 1'b1;
@(posedge clk); start_g <= 1'b0;
wait (done_g);
@(posedge clk);
$display("");
$display("CONFIG G: N_INPUTS=%0d PARALLEL=%0d (GROUPS=%0d) -- close second per benchmark",
G_N_INPUTS, G_PARALLEL, G_N_INPUTS/G_PARALLEL);
$display(" y = %0d expected = 32", y_g);
if (y_g !== 8'sd32) begin
$display(" FAIL");
errors = errors + 1;
end else begin
$display(" PASS");
end
$display("");
$display("========================================");
$display("PARAMETER SWEEP SUMMARY");
$display(" errors = %0d", errors);
$display(" findings = %0d (documented limitations, not bench failures)", findings);
if (errors == 0)
$display("PARAMETER SWEEP: PASSED (all configs behaved as characterized)");
$display("PARAMETER SWEEP: PASSED (%0d valid configs)", 4);
else
$display("PARAMETER SWEEP: FAILED");
$display("PARAMETER SWEEP: FAILED (%0d errors)", errors);
$display("========================================");
$display("");