diff --git a/CHANGELOG.md b/CHANGELOG.md index 542eb153d..4fa805b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added + + - Add `vmv_x_s_hazard` app: regression reproducer for the VWXUNARY0 vs1 hazard (#436) + - Add `vlseg_eew` app: Spike-vs-Ara differential probe for the segment-load EEW tracker bug (#453) + - Add `vzext_vf8` app: Spike-vs-Ara differential probe for the vzext/vsext.vf8 source-width bug (#452) + - Add `vmerge_v0_illegal` app: Spike-vs-Ara trap probe for the masked vmerge vd=v0 legality check (#460) + ### Fixed + - Clear `use_vs1` for VWXUNARY0 (`vmv.x.s`, `vcpop.m`, `vfirst.m`) to avoid a spurious vs1 hazard (#436) + - Tag all destination registers of segment loads/stores in the EEW tracker, fixing corrupted vd+1..vd+nf read-back (#453) + - Force the `vsext.vf8`/`vzext.vf8` source element width to EW8 instead of the tracked eew, fixing a wrong result when the source register's last-written eew was not the 1/8-width view (#452) + - Raise illegal-instruction for a masked `vmerge`/`vfmerge` whose destination is `v0` (overlaps the mask source), matching the RVV vd/v0 overlap rule; `vmv.v.*`/`vfmv.v.f` (vm=1) stay legal (#460) - 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/vlseg_eew/main.c b/apps/vlseg_eew/main.c new file mode 100644 index 000000000..917449a0b --- /dev/null +++ b/apps/vlseg_eew/main.c @@ -0,0 +1,60 @@ +// 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 #453 (segment load corrupts +// vd+1..vd+nf on read-back when EEW != EW8). +// +// vlseg4e16.v writes v8..v11. The per-vreg EEW tracker only tagged the base vd, +// so reading v9/v10/v11 back triggered a spurious on-read reshuffle that +// byte-packs the data. We load 4 segments of 4 fields, store each field, and +// print them; diff Spike vs Ara. + +#include + +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +static volatile uint16_t in_buf[16]; // 4 elements x 4 fields, element-major +static volatile uint16_t f0[4], f1[4], f2[4], f3[4]; + +int main() { + uint64_t vl; + + // in_buf[i*4 + j] = base[j] + i, with base = {1, 11, 21, 31} + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + in_buf[i * 4 + j] = (uint16_t)((j * 10 + 1) + i); + + asm volatile("vsetivli %0, 4, e16, m1, ta, ma" : "=r"(vl)); + asm volatile("vlseg4e16.v v8, (%0)" ::"r"(in_buf)); + asm volatile("vse16.v v8, (%0)" ::"r"(f0) : "memory"); + asm volatile("vse16.v v9, (%0)" ::"r"(f1) : "memory"); + asm volatile("vse16.v v10, (%0)" ::"r"(f2) : "memory"); + asm volatile("vse16.v v11, (%0)" ::"r"(f3) : "memory"); + + // Expected: f0={1,2,3,4} f1={11,12,13,14} f2={21,22,23,24} f3={31,32,33,34} + for (int i = 0; i < 4; i++) printf("a%d=%d\n", i, (int)f0[i]); + for (int i = 0; i < 4; i++) printf("b%d=%d\n", i, (int)f1[i]); + for (int i = 0; i < 4; i++) printf("c%d=%d\n", i, (int)f2[i]); + for (int i = 0; i < 4; i++) printf("d%d=%d\n", i, (int)f3[i]); + return 0; +} diff --git a/apps/vmerge_v0_illegal/main.c b/apps/vmerge_v0_illegal/main.c new file mode 100644 index 000000000..5e6cccb44 --- /dev/null +++ b/apps/vmerge_v0_illegal/main.c @@ -0,0 +1,60 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// SPDX-License-Identifier: Apache-2.0 +// +// Spike-vs-Ara differential trap probe for issue #460: a masked vmerge whose +// destination is v0 (also the mask source) must raise an illegal-instruction +// exception (mcause=2). vmv.v.* (vm=1) with vd=v0 stays legal. +// +// vsetivli x8, 10, e8, m4 +// vmv.v.x v0, -1 ; legal (vm=1), must NOT trap +// vmv.v.x v20, 0x33 ; legal +// marker = 1 ; proves setup did not trap +// vmerge.vim v0, v20, -2, v0 ; illegal -> trap, mcause=2 +// Expected (both): cause=2, marker=1. + +#include +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +volatile uint64_t g_cause = 0xbad; +volatile uint64_t g_marker = 0; + +asm(".global mtvec_handler\n" + ".align 2\n" + "mtvec_handler:\n" + " addi sp, sp, -16\n" + " sd t0, 0(sp)\n" + " sd t1, 8(sp)\n" + " csrr t0, mcause\n la t1, g_cause\n sd t0, 0(t1)\n" + " la t0, trap_resume\n csrw mepc, t0\n" + " ld t0, 0(sp)\n ld t1, 8(sp)\n addi sp, sp, 16\n" + " mret\n"); + +extern char trap_resume[]; +uintptr_t handle_trap(uintptr_t cause, uintptr_t epc, uintptr_t regs) { + (void)epc; + (void)regs; + g_cause = cause; + return (uintptr_t)trap_resume; +} + +int main() { + asm volatile("fence" ::: "memory"); + asm volatile("vsetivli x8, 10, e8, m4"); + asm volatile("li t0, -1\n vmv.v.x v0, t0" ::: "t0"); // legal (vm=1) + asm volatile("li t0, 0x33\n vmv.v.x v20, t0" ::: "t0"); // legal + g_marker = 1; // setup did not trap + asm volatile("vmerge.vim v0, v20, -2, v0\n" // illegal + ".global trap_resume\n" + "trap_resume:\n"); + asm volatile("fence" ::: "memory"); + + printf("cause=%d marker=%d\n", (int)g_cause, (int)g_marker); + return 0; +} diff --git a/apps/vmv_x_s_hazard/main.c b/apps/vmv_x_s_hazard/main.c new file mode 100644 index 000000000..6b1f69c2e --- /dev/null +++ b/apps/vmv_x_s_hazard/main.c @@ -0,0 +1,82 @@ +// 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. + +// Regression reproducer for issue #436. +// +// The VWXUNARY0 instructions (vmv.x.s, vcpop.m, vfirst.m) are decoded in the +// OPMVV path of ara_dispatcher.sv, where ara_req.use_vs1 defaults to 1'b1. +// Their rs1 field encodes the *sub-opcode*, not a vector register: +// +// vmv.x.s -> rs1 = 0b00000 (0) -> a buggy dispatcher reads a false vs1 = v0 +// vcpop.m -> rs1 = 0b10000 (16) -> false vs1 = v16 +// vfirst.m -> rs1 = 0b10001 (17) -> false vs1 = v17 +// +// Leaving use_vs1 asserted makes the main sequencer raise a spurious RAW hazard +// against whatever register number occupies the rs1 field. The dominant failure +// mode is serialization or a deadlock (not data corruption), so: +// * a hang is caught by the testbench cycle-timeout, and +// * we additionally self-check the returned scalars to guard against any +// regression that would corrupt the result while "fixing" the hazard. +// +// This test deliberately writes v0 immediately before each vmv.x.s that reads +// an unrelated source register, building a tight producer/consumer chain on the +// register the bug falsely depends on. + +#include + +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +#define N_ITERS 8 + +int main() { + uint64_t vl; + int errors = 0; + + // SEW = 64, LMUL = 1 + asm volatile("vsetvli %0, zero, e64, m1, ta, ma" : "=r"(vl)); + + // Tight chain: write v0 (the register vmv.x.s falsely depends on), write the + // real source v8, then read back element 0 from v8 via vmv.x.s. A correct + // dispatcher does not stall on v0 here. + for (int i = 0; i < N_ITERS; i++) { + uint64_t poison = 0xAAAA000000000000ULL | (uint64_t)i; // goes into v0 + uint64_t src = 0x1122334455660000ULL | (uint64_t)i; // goes into v8 + uint64_t out; + + asm volatile("vmv.s.x v0, %0" ::"r"(poison)); // false-dependency target + asm volatile("vmv.s.x v8, %0" ::"r"(src)); // actual source element 0 + asm volatile("vmv.x.s %0, v8" : "=r"(out)); // must read v8, not wait on v0 + + if (out != src) { + printf("vmv.x.s iter %d: got 0x%lx, expected 0x%lx\n", i, out, src); + errors++; + } + } + + if (errors == 0) + printf("vmv_x_s_hazard: PASS\n"); + else + printf("vmv_x_s_hazard: FAIL (%d errors)\n", errors); + + return errors; +} diff --git a/apps/vzext_vf8/main.c b/apps/vzext_vf8/main.c new file mode 100644 index 000000000..412b9f186 --- /dev/null +++ b/apps/vzext_vf8/main.c @@ -0,0 +1,32 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// SPDX-License-Identifier: Apache-2.0 +// +// Spike-vs-Ara differential probe for issue #452 (vzext.vf8 reads the wrong +// source width). v28[0] (e16) = 0xa04f; vzext.vf8 to e64 should zero-extend the +// 8-bit sub-elements: v14[0]=0x4f, v14[1]=0xa0. Register-only (no race). + +#include +#ifdef SPIKE +#include "util.h" +#include +#elif defined ARA_LINUX +#include +#else +#include "printf.h" +#endif + +int main() { + uint64_t x20, x21; + uint64_t v = 0xa04f; + + asm volatile("vsetivli x8, 1, e16, mf4, ta, ma"); + asm volatile("vmv.s.x v28, %0" ::"r"(v)); + asm volatile("vsetivli x8, 27, e64, m1, ta, ma"); + asm volatile("vzext.vf8 v14, v28"); + asm volatile("vmv.x.s %0, v14" : "=r"(x20)); // expect 0x4f = 79 + asm volatile("vslidedown.vi v15, v14, 1"); + asm volatile("vmv.x.s %0, v15" : "=r"(x21)); // expect 0xa0 = 160 + + printf("x20=%d x21=%d\n", (int)x20, (int)x21); + return 0; +} diff --git a/hardware/src/ara_dispatcher.sv b/hardware/src/ara_dispatcher.sv index e5f9e06d5..491ee8c47 100644 --- a/hardware/src/ara_dispatcher.sv +++ b/hardware/src/ara_dispatcher.sv @@ -844,6 +844,11 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( 6'b010111: begin ara_req.op = ara_pkg::VMERGE; ara_req.use_vs2 = !insn.varith_type.vm; // vmv.v.v does not use vs2 + // A masked vmerge (vm=0) writes a full vector, so its vd + // must not overlap the mask source v0. vmv.v.v (vm=1) is + // exempt. (#460) + if (!insn.varith_type.vm && insn.varith_type.rd == 5'd0) + illegal_insn = 1'b1; // With a normal vmv.v.v, copy input eew to output // to avoid unnecessary reshuffles if (insn.varith_type.vm) begin @@ -1120,6 +1125,9 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( 6'b010111: begin ara_req.op = ara_pkg::VMERGE; ara_req.use_vs2 = !insn.varith_type.vm; // vmv.v.x does not use vs2 + // Masked vmerge vd must not overlap the mask source v0. (#460) + if (!insn.varith_type.vm && insn.varith_type.rd == 5'd0) + illegal_insn = 1'b1; end 6'b100000: ara_req.op = ara_pkg::VSADDU; 6'b100001: ara_req.op = ara_pkg::VSADD; @@ -1320,6 +1328,9 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( 6'b010111: begin ara_req.op = ara_pkg::VMERGE; ara_req.use_vs2 = !insn.varith_type.vm; // vmv.v.i does not use vs2 + // Masked vmerge vd must not overlap the mask source v0. (#460) + if (!insn.varith_type.vm && insn.varith_type.rd == 5'd0) + illegal_insn = 1'b1; end 6'b100000: ara_req.op = ara_pkg::VSADDU; 6'b100001: ara_req.op = ara_pkg::VSADD; @@ -1497,6 +1508,13 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( acc_resp_o.req_ready = 1'b0; acc_resp_o.resp_valid = 1'b0; + // VWXUNARY0 (vmv.x.s, vcpop.m, vfirst.m) do not read vs1: + // the rs1 field encodes the sub-opcode, not a vector register. + // Leaving use_vs1 asserted (the OPMVV default) makes the main + // sequencer detect a spurious hazard against whatever register + // number happens to sit in the rs1 field. + ara_req.use_vs1 = 1'b0; + case (insn.varith_type.rs1) 5'b00000: begin ara_req.op = ara_pkg::VMVXS; @@ -1660,7 +1678,12 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( case (insn.varith_type.rs1) 5'b00010: begin // VZEXT.VF8 ara_req.conversion_vs2 = OpQueueConversionZExt8; - ara_req.eew_vs2 = eew_q[insn.varith_type.rs2]; + // The source is 1/8 the destination width. VF8 is only legal + // at SEW=e64, so the source elements are always EW8. Using the + // tracked eew_q here was wrong: a stale eew (e.g. e16) makes the + // ZExt8 opqueue conversion (which only handles EW8) fall through, + // leaving the operand unconverted. (#452) + ara_req.eew_vs2 = EW8; ara_req.cvt_resize = CVT_WIDE; ara_req.emul = csr_vtype_q.vlmul; lmul_vs2 = prev_lmul(prev_lmul(prev_lmul(csr_vtype_q.vlmul))); @@ -1672,7 +1695,8 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( end 5'b00011: begin // VSEXT.VF8 ara_req.conversion_vs2 = OpQueueConversionSExt8; - ara_req.eew_vs2 = eew_q[insn.varith_type.rs2]; + // See VZEXT.VF8 above: source is always EW8 (VF8 needs e64). (#452) + ara_req.eew_vs2 = EW8; ara_req.cvt_resize = CVT_WIDE; ara_req.emul = csr_vtype_q.vlmul; lmul_vs2 = prev_lmul(prev_lmul(prev_lmul(csr_vtype_q.vlmul))); @@ -2657,7 +2681,13 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( // This instruction ignores LMUL checks skip_lmul_checks = 1'b1; end - 6'b010111: ara_req.op = ara_pkg::VMERGE; + 6'b010111: begin + ara_req.op = ara_pkg::VMERGE; + // Masked vfmerge vd must not overlap the mask source v0; + // vfmv.v.f (vm=1) is exempt. (#460) + if (!insn.varith_type.vm && insn.varith_type.rd == 5'd0) + illegal_insn = 1'b1; + end 6'b011000: begin ara_req.op = ara_pkg::VMFEQ; ara_req.use_vd_op = 1'b1; @@ -3699,38 +3729,29 @@ module ara_dispatcher import ara_pkg::*; import rvv_pkg::*; #( // Update the EEW if (ara_req_valid_d && ara_req.use_vd && ara_req_ready_i) begin + automatic int unsigned regs_per_emul; + automatic int unsigned regs_to_tag; + automatic logic is_seg_mem_op; unique case (ara_req.emul) - LMUL_1: begin - for (int i = 0; i < 1; i++) begin - eew_d[ara_req.vd + i] = ara_req.vtype.vsew; - eew_valid_d[ara_req.vd + i] = 1'b1; - end - end - LMUL_2: begin - for (int i = 0; i < 2; i++) begin - eew_d[ara_req.vd + i] = ara_req.vtype.vsew; - eew_valid_d[ara_req.vd + i] = 1'b1; - end - end - LMUL_4: begin - for (int i = 0; i < 4; i++) begin - eew_d[ara_req.vd + i] = ara_req.vtype.vsew; - eew_valid_d[ara_req.vd + i] = 1'b1; - end - end - LMUL_8: begin - for (int i = 0; i < 8; i++) begin - eew_d[ara_req.vd + i] = ara_req.vtype.vsew; - eew_valid_d[ara_req.vd + i] = 1'b1; - end - end - default: begin // EMUL < 1 - for (int i = 0; i < 1; i++) begin - eew_d[ara_req.vd + i] = ara_req.vtype.vsew; - eew_valid_d[ara_req.vd + i] = 1'b1; - end - end + LMUL_2: regs_per_emul = 2; + LMUL_4: regs_per_emul = 4; + LMUL_8: regs_per_emul = 8; + default: regs_per_emul = 1; // LMUL_1 and EMUL < 1 endcase + // Segment loads/stores write (nf+1) groups of EMUL consecutive registers + // (vd, vd+EMUL, ...). Without tagging them all, vd+1..vd+nf keep the + // reset-default EW8 and a later read triggers a spurious on-read reshuffle + // that byte-packs the data (#453). + is_seg_mem_op = (ara_req.op inside {VLE, VLSE, VLXE, VSE, VSSE, VSXE}) + && (ara_req.nf != 3'b000); + regs_to_tag = is_seg_mem_op ? regs_per_emul * (int'(ara_req.nf) + 1) + : regs_per_emul; + for (int i = 0; i < 32; i++) begin + if (i < regs_to_tag) begin + eew_d[ara_req.vd + i] = ara_req.vtype.vsew; + eew_valid_d[ara_req.vd + i] = 1'b1; + end + end end // Any valid non-config instruction is a NOP if vl == 0, with some exceptions,