Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 81 additions & 0 deletions apps/vcompress_sweep/main.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#ifdef SPIKE
#include "util.h"
#include <stdio.h>
#elif defined ARA_LINUX
#include <stdio.h>
#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;
}
51 changes: 51 additions & 0 deletions apps/vcpop_vl/main.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#ifdef SPIKE
#include "util.h"
#include <stdio.h>
#elif defined ARA_LINUX
#include <stdio.h>
#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;
}
64 changes: 64 additions & 0 deletions apps/vid_drain_deadlock/main.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#ifdef SPIKE
#include "util.h"
#include <stdio.h>
#elif defined ARA_LINUX
#include <stdio.h>
#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;
}
44 changes: 44 additions & 0 deletions apps/vid_m4_hang/main.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#ifdef SPIKE
#include "util.h"
#include <stdio.h>
#elif defined ARA_LINUX
#include <stdio.h>
#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;
}
25 changes: 25 additions & 0 deletions hardware/src/masku/masku.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion hardware/src/masku/masku_operands.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down