diff --git a/CHANGELOG.md b/CHANGELOG.md index 542eb153d..bb76d5475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added + + - Add `vid_drain_deadlock` app: differential reproducer for the mask-unit VID drain bug (#448) + - Add `vid_m4_hang` app: regression reproducer for the vid.v LMUL=4 hang (#437, closed by the #448 fix) + - Add `vcpop_vl` app: Spike-vs-Ara differential probe for the vcpop VL-trim bug (#446) + - Add `vcompress_sweep` app: self-checking regression test for the vcompress completion hang (#450) + ### Fixed + - Trim `vcpop.m`/`vfirst.m` operand to the active element range, fixing wrong counts when vl < VLMAX (#446) + - Tag the last selected vcompress element as the terminator (last_idx/is_last_req), fixing a hang when trailing elements are unselected (#450) + - Drain ALU operands in the mask unit during `vid.v` to avoid a lane deadlock (#448, also closes #437) - Fix dump vtrace script for vsetvli instructions without x0 (ideal dispatcher) - Fix Pathfinder and FFT performance - Stall Ara and wait for ara_idle upon CSR write/read diff --git a/apps/vcompress_sweep/main.c b/apps/vcompress_sweep/main.c new file mode 100644 index 000000000..73f1f0878 --- /dev/null +++ b/apps/vcompress_sweep/main.c @@ -0,0 +1,81 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// SPDX-License-Identifier: Apache-2.0 +// +// Self-checking regression test for issue #450 (vcompress hang). Covers the +// reported trailing-unselected case plus last-selected, all-selected, a +// multi-word vl, and the all-unselected (compress-to-0) edge. Built from vector +// ops only. Expected values verified against Spike. Returns nonzero on mismatch; +// a hang is caught by the testbench timeout. + +#include +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +static volatile uint32_t out[24]; + +static int check4(const char *tag, int e0, int e1, int e2, int e3) { + if (out[0] == (uint32_t)e0 && out[1] == (uint32_t)e1 && + out[2] == (uint32_t)e2 && out[3] == (uint32_t)e3) + return 0; + printf("%s FAIL: {%d,%d,%d,%d}\n", tag, (int)out[0], (int)out[1], (int)out[2], (int)out[3]); + return 1; +} + +int main() { + uint64_t vl; + int err = 0; + + // Case A: even selected -> last element unselected (the reported bug). {0,2,4,6} + asm volatile("vsetivli %0, 8, e32, m1, ta, ma" : "=r"(vl)); + asm volatile("vid.v v2"); + asm volatile("vand.vi v3, v2, 1"); + asm volatile("vmseq.vi v6, v3, 0"); + asm volatile("vcompress.vm v4, v2, v6"); + asm volatile("vse32.v v4, (%0)" ::"r"(out) : "memory"); + err += check4("A", 0, 2, 4, 6); + + // Case B: odd selected -> last element selected (regression guard). {1,3,5,7} + asm volatile("vid.v v2"); + asm volatile("vand.vi v3, v2, 1"); + asm volatile("vmsne.vi v6, v3, 0"); + asm volatile("vcompress.vm v4, v2, v6"); + asm volatile("vse32.v v4, (%0)" ::"r"(out) : "memory"); + err += check4("B", 1, 3, 5, 7); + + // Case C: all selected -> identity {0,1,2,3} + asm volatile("vid.v v2"); + asm volatile("vmset.m v6"); + asm volatile("vcompress.vm v4, v2, v6"); + asm volatile("vse32.v v4, (%0)" ::"r"(out) : "memory"); + err += check4("C", 0, 1, 2, 3); + + // Case E: multi-word vl=20, even selected -> {0,2,...,18}; check out[0,1,9] + asm volatile("vsetvli %0, %1, e32, m1, ta, ma" : "=r"(vl) : "r"((uint64_t)20)); + asm volatile("vid.v v2"); + asm volatile("vand.vi v3, v2, 1"); + asm volatile("vmseq.vi v6, v3, 0"); + asm volatile("vcompress.vm v4, v2, v6"); + asm volatile("vse32.v v4, (%0)" ::"r"(out) : "memory"); + if (!(out[0] == 0 && out[1] == 2 && out[9] == 18)) { + printf("E FAIL: out[0]=%d out[1]=%d out[9]=%d\n", (int)out[0], (int)out[1], (int)out[9]); + err++; + } + + // Case D: none selected (vl=8) -> compress to length 0; must complete. + asm volatile("vsetivli %0, 8, e32, m1, ta, ma" : "=r"(vl)); + asm volatile("vid.v v2"); + asm volatile("vmclr.m v6"); + asm volatile("vcompress.vm v4, v2, v6"); + + if (err == 0) + printf("vcompress_sweep: PASS\n"); + else + printf("vcompress_sweep: FAIL (%d)\n", err); + return err; +} diff --git a/apps/vcpop_vl/main.c b/apps/vcpop_vl/main.c new file mode 100644 index 000000000..e7ed53ae5 --- /dev/null +++ b/apps/vcpop_vl/main.c @@ -0,0 +1,51 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Spike-vs-Ara differential probe for issue #446 (vcpop.m ignores VL). +// +// vmset.m sets the active (vl) mask bits; the tail beyond vl is mask-agnostic. +// vcpop.m must count only the first vl bits. The bug counts tail bits too, so +// Ara reports more than vl while Spike reports exactly vl. Each line prints the +// computed population count for a given vl; diff the Spike and Ara outputs. + +#include + +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +#define PROBE(VLNUM) \ + do { \ + uint64_t vl, cnt; \ + asm volatile("vsetivli %0, " #VLNUM ", e32, m1, ta, ma" : "=r"(vl)); \ + asm volatile("vmset.m v0"); \ + asm volatile("vcpop.m %0, v0" : "=r"(cnt)); \ + printf("vl=%d vcpop=%d\n", (int)vl, (int)cnt); \ + } while (0) + +int main() { + PROBE(1); + PROBE(2); + PROBE(4); + PROBE(8); + PROBE(16); + return 0; +} diff --git a/apps/vid_drain_deadlock/main.c b/apps/vid_drain_deadlock/main.c new file mode 100644 index 000000000..972047495 --- /dev/null +++ b/apps/vid_drain_deadlock/main.c @@ -0,0 +1,64 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Differential reproducer for issue #448 (mask unit ALU-operand drain on vid.v). +// +// vid.v does not consume an ALU operand, but lanes can still drive ALU data into +// the mask-unit operand path. If it is never acknowledged, the stale operand +// state corrupts the following mask flow. With the fix in masku_operands.sv +// (drain ALU operands during VID) the masked merge is correct; without it the +// merge under v0 is dropped (observed here as a wrong element-2 result; the same +// drain bug can also deadlock under different timing). +// +// Mirrors the reported sequence (vid.v -> mask compare -> masked merge) and +// self-checks: vfmerge must write 9.0f (0x41100000) into masked element 2. + +#include + +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +static volatile uint32_t mask_out[1]; +static volatile uint32_t out[4]; + +int main() { + uint64_t vl; + float f = 9.0f; + + asm volatile("vsetivli %0, 4, e32, m1, ta, ma" : "=r"(vl)); + asm volatile("vmv.v.i v8, 7"); // stand-in for a vle32.v load + asm volatile("vid.v v9"); // v9 = {0,1,2,3} + asm volatile("vmseq.vi v0, v9, 2"); // mask: element 2 set + asm volatile("vsm.v v0, (%0)" ::"r"(mask_out) : "memory"); + asm volatile("vfmerge.vfm v8, v8, %0, v0" ::"f"(f)); // merge under mask + asm volatile("vse32.v v8, (%0)" ::"r"(out) : "memory"); + + // Masked merge must write 9.0f (0x41100000) into element 2; others stay 7. + uint32_t expected = 0x41100000u; // 9.0f + if (out[2] == expected && out[0] == 7 && out[1] == 7 && out[3] == 7) { + printf("vid_drain_deadlock: PASS (out[2]=0x%x)\n", out[2]); + return 0; + } + printf("vid_drain_deadlock: FAIL (out={%u,%u,0x%x,%u}, expected out[2]=0x%x)\n", + out[0], out[1], out[2], out[3], expected); + return 1; +} diff --git a/apps/vid_m4_hang/main.c b/apps/vid_m4_hang/main.c new file mode 100644 index 000000000..89d054ee0 --- /dev/null +++ b/apps/vid_m4_hang/main.c @@ -0,0 +1,44 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Probe for issue #437 (vid.v hang). Mirrors the reported sequence: a widening +// multiply followed by vid.v at LMUL=4 hangs a buggy Ara. Reaching the print +// means no deadlock. This shares the mask-unit VID path with #448. + +#include + +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +int main() { + uint64_t vl, res; + + asm volatile("vsetvli %0, zero, e16, m4, ta, ma" : "=r"(vl)); + asm volatile("vmv.v.i v0, 2"); + asm volatile("vwmul.vx v8, v0, %0" ::"r"(0L)); // widening -> v8 group (e32, m8) + asm volatile("vid.v v0"); // <- reported hang point + asm volatile("vadd.vi v0, v0, 1"); + asm volatile("vmv.x.s %0, v0" : "=r"(res)); // v0 = {0,1,2,...}+1 -> res = 1 + + printf("vid_m4=%d\n", (int)res); + return 0; +} diff --git a/hardware/src/masku/masku.sv b/hardware/src/masku/masku.sv index d2fdccdc2..f1101d4ad 100644 --- a/hardware/src/masku/masku.sv +++ b/hardware/src/masku/masku.sv @@ -858,6 +858,12 @@ module masku import ara_pkg::*; import rvv_pkg::*; #( // VCPOP, VFIRST: mask the current slice and feed the popc or lzc unit [VCPOP:VFIRST] : begin vcpop_operand = (!vinsn_issue.vm) ? masku_operand_alu_seq & masku_operand_m_seq : masku_operand_alu_seq; + // #446: do not count/scan mask bits past the valid element range. + // issue_cnt_q + in_ready_cnt_q*delta_elm_q reconstructs the element + // count at the start of the current word, i.e. the last valid index. + for (int unsigned i = 0; i < NrLanes*DataWidth; i++) + if (i >= (vlen_t'(issue_cnt_q) + (vlen_t'(in_ready_cnt_q) * vlen_t'(delta_elm_q)))) + vcpop_operand[i] = 1'b0; end default:; endcase @@ -1136,6 +1142,25 @@ module masku import ara_pkg::*; import rvv_pkg::*; #( vrgat_req_fifo_push = 1'b1; // Increase the number of elements to write vcompress_cnt_d = vcompress_cnt_q + 1; + // #450: tag the LAST selected element's FIFO entry as the terminator. + // The terminator carries both vcompress_last_idx (so the masku + // completes) and is_last_req (so the lane VRGATHER FSM returns to + // IDLE). Previously these only rode the last *input* element, which + // is never pushed when trailing elements are unselected -> hang. + // A selected element is the last one iff no mask bit is set in + // (vrgat_cnt, vl). The whole mask is in masku_operand_alu_seq when + // vl fits in one word; for larger vl keep the legacy behavior. + if (vinsn_issue.vl <= NrLanes*DataWidth) begin + automatic logic more_after; + more_after = 1'b0; + for (int unsigned i = 0; i < NrLanes*DataWidth; i++) + if ((i > vrgat_cnt_q) && (i < vinsn_issue.vl)) + more_after |= masku_operand_alu_seq[i]; + if (!more_after) begin + vcompress_last_idx_d = 1'b1; + vrgat_req_is_last_req_d = 1'b1; + end + end end end end diff --git a/hardware/src/masku/masku_operands.sv b/hardware/src/masku/masku_operands.sv index 2788652c7..f355a1979 100644 --- a/hardware/src/masku/masku_operands.sv +++ b/hardware/src/masku/masku_operands.sv @@ -249,7 +249,13 @@ module masku_operands import ara_pkg::*; import rvv_pkg::*; #( for (int lane = 0; lane < NrLanes; lane++) begin // Acknowledge alu operand for (int operand_fu = 0; operand_fu < NrMaskFUnits; operand_fu++) begin - masku_operand_ready_o[lane][2 + operand_fu] = (masku_fu_e'(operand_fu) == masku_fu_i) && masku_operand_alu_ready_i[lane]; + // VID does not consume an ALU operand, but the lanes can still push ALU + // data into the operand path. If it is never acknowledged, the producing + // lane stalls forever and the mask unit deadlocks. Drain it during VID. + if (vinsn_issue_i.op == VID) + masku_operand_ready_o[lane][2 + operand_fu] = 1'b1; + else + masku_operand_ready_o[lane][2 + operand_fu] = (masku_fu_e'(operand_fu) == masku_fu_i) && masku_operand_alu_ready_i[lane]; end // Acknowledge vd operands masku_operand_ready_o[lane][1] = masku_operand_vd_lane_ready[lane];