Skip to main content

ab_riscv_interpreter/v/zvexx/mask/
zvexx_mask_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_config::{BoundedVl, VectorConfig};
4use crate::v::vector_registers::{VRegGroup, VectorRegistersExt};
5use crate::v::zvexx::arith::zvexx_arith_helpers::write_mask_bit;
6use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
7use ab_riscv_primitives::prelude::*;
8
9/// Execute a mask-register logical operation (§16.1).
10///
11/// Computes the result for the body elements `[0, vl)` only, tail bits `[vl, VLEN)` follow the
12/// tail-agnostic policy, realised here as undisturbed (a permitted agnostic implementation and the
13/// one the reference model produces). `op` receives `(vs2_bit: bool, vs1_bit: bool) -> bool`.
14///
15/// Both sources are snapshotted before writing, so `vd` may overlap either of them.
16#[inline(always)]
17#[doc(hidden)]
18#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
19pub fn execute_mask_logical_op<Reg, Env, F>(
20    env: &mut Env,
21    config: VectorConfig<Env::Hart>,
22    vd: VReg,
23    vs2: VReg,
24    vs1: VReg,
25    op: F,
26) where
27    Reg: Register,
28    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
29    F: Fn(bool, bool) -> bool,
30{
31    let vl = config.vl();
32    // Snapshot both sources before writing to handle vd overlapping vs2 or vs1
33    let vs2_snap = *env.read_vregs().get(vs2);
34    let vs1_snap = *env.read_vregs().get(vs1);
35    // Body elements [0, vl): compute the logical operation bit-by-bit. Tail bits [vl, VLEN) are
36    // left undisturbed.
37    for i in vl.indices() {
38        let a = mask_bit(&vs2_snap, i);
39        let b = mask_bit(&vs1_snap, i);
40        write_mask_bit(env.write_vregs(), vd, i, op(a, b))
41            .expect("`i < vl <= VLEN`, so the mask bit is within `vd`; qed");
42    }
43    env.mark_vs_dirty();
44}
45
46/// Execute `vcpop.m`: count set bits in vs2 for active elements `0..vl`, write
47/// result to `rd`.
48///
49/// Per spec §16.2: `rd` receives the number of mask bits set in `vs2`, considering only elements
50/// `0..vl` that are active under the mask.
51///
52/// Returns `rd_value`.
53#[inline(always)]
54#[doc(hidden)]
55#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
56pub fn execute_vcpop<Reg, Env>(
57    env: &mut Env,
58    config: VectorConfig<Env::Hart>,
59    vs2: VReg,
60    vm: bool,
61) -> Reg::Type
62where
63    Reg: Register,
64    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
65{
66    let vl = config.vl().get();
67    let mask_buf = snapshot_mask(env.read_vregs(), vm);
68    let vs2_reg = *env.read_vregs().get(vs2);
69    let mut count = 0u32;
70    for i in Vstart::ZERO.range_to(vl) {
71        if !mask_bit(&mask_buf, i) {
72            continue;
73        }
74        if mask_bit(&vs2_reg, i) {
75            count += 1;
76        }
77    }
78
79    env.mark_vs_dirty();
80
81    Reg::Type::from(count)
82}
83
84/// Execute `vfirst.m`: find the index of the first set bit in vs2 for active elements
85/// `0..vl`, write result (or -1 if none) to `rd`.
86///
87/// Per spec §16.3: `rd` receives the element index of the lowest-numbered active set bit, or
88/// `-1` (all-ones) if no active element of vs2 is set.
89///
90/// Returns `rd_value`.
91#[inline(always)]
92#[doc(hidden)]
93#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
94pub fn execute_vfirst<Reg, Env>(
95    env: &mut Env,
96    config: VectorConfig<Env::Hart>,
97    vs2: VReg,
98    vm: bool,
99) -> Reg::Type
100where
101    Reg: Register,
102    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
103{
104    let vl = config.vl().get();
105    let mask_buf = snapshot_mask(env.read_vregs(), vm);
106    let vs2_reg = *env.read_vregs().get(vs2);
107    // -1 encoded as all-ones for the register width; `Into<u64>` on XLEN-wide type then back
108    let not_found = u64::MAX;
109    let mut result = not_found;
110    for i in Vstart::ZERO.range_to(vl) {
111        if !mask_bit(&mask_buf, i) {
112            continue;
113        }
114        if mask_bit(&vs2_reg, i) {
115            result = u64::from(i);
116            break;
117        }
118    }
119    // Write -1 (all-ones for XLEN bits) or the found index.
120    // The spec requires -1 as a signed XLEN-wide value, meaning all bits set.
121    // `!Reg::Type::from(0)` produces all-ones for both u32 (RV32) and u64 (RV64)
122    // without depending on `From<u64>` (which is not in the `Register` trait bounds).
123    // For the found index, element indices fit in u32 since vl <= VLEN <= 2^32.
124    let rd_value = if result == not_found {
125        !Reg::Type::from(0u8)
126    } else {
127        Reg::Type::from(result as u32)
128    };
129    env.mark_vs_dirty();
130
131    rd_value
132}
133
134/// Execute `vmsbf.m`: set all mask bits before (not including) the first set bit of vs2.
135///
136/// Per spec §16.4: for each element `i` in `0..vl`, if no prior active set bit exists in
137/// vs2, the destination bit is set; once the first set bit in vs2 is encountered, all subsequent
138/// destination bits are cleared.
139///
140/// Inactive elements (masked off) are left undisturbed. Tail elements are undisturbed.
141#[inline(always)]
142#[doc(hidden)]
143#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
144pub fn execute_vmsbf<Reg, Env>(
145    env: &mut Env,
146    vd: VReg,
147    vs2: VReg,
148    vm: bool,
149    vl: BoundedVl<Env::Hart>,
150) where
151    Reg: Register,
152    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
153{
154    let mask_buf = snapshot_mask(env.read_vregs(), vm);
155    let vs2_snap = *env.read_vregs().get(vs2);
156    let mut found_first = false;
157    for i in vl.indices() {
158        // Inactive elements: undisturbed
159        if !mask_bit(&mask_buf, i) {
160            continue;
161        }
162        let vs2_bit = mask_bit(&vs2_snap, i);
163        // vmsbf: set bits strictly *before* the first set bit; clear from first set bit onward
164        let result = !found_first && !vs2_bit;
165        if vs2_bit {
166            found_first = true;
167        }
168        write_mask_bit(env.write_vregs(), vd, i, result)
169            .expect("`i < vl <= VLEN`, so the mask bit is within `vd`; qed");
170    }
171    env.mark_vs_dirty();
172}
173
174/// Execute `vmsof.m`: set only the first set bit position of vs2, clear all others.
175///
176/// Per spec §16.5: the destination bit is set only at the lowest-numbered active element where
177/// vs2 has a set bit. All other active destination bits are cleared.
178#[inline(always)]
179#[doc(hidden)]
180#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
181pub fn execute_vmsof<Reg, Env>(
182    env: &mut Env,
183    vd: VReg,
184    vs2: VReg,
185    vm: bool,
186    vl: BoundedVl<Env::Hart>,
187) where
188    Reg: Register,
189    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
190{
191    let mask_buf = snapshot_mask(env.read_vregs(), vm);
192    let vs2_snap = *env.read_vregs().get(vs2);
193    let mut found_first = false;
194    for i in vl.indices() {
195        if !mask_bit(&mask_buf, i) {
196            continue;
197        }
198        let vs2_bit = mask_bit(&vs2_snap, i);
199        // vmsof: set only the first set bit position; clear all others (including after first)
200        let result = !found_first && vs2_bit;
201        if vs2_bit && !found_first {
202            found_first = true;
203        }
204        write_mask_bit(env.write_vregs(), vd, i, result)
205            .expect("`i < vl <= VLEN`, so the mask bit is within `vd`; qed");
206    }
207    env.mark_vs_dirty();
208}
209
210/// Execute `vmsif.m`: set all mask bits up to and including the first set bit of vs2.
211///
212/// Per spec §16.6: for each active element, the destination bit is set if no prior active set bit
213/// in vs2 has been seen yet *or* the current element itself is set; it is cleared once a set bit
214/// has been seen and the current element is past it.
215#[inline(always)]
216#[doc(hidden)]
217#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
218pub fn execute_vmsif<Reg, Env>(
219    env: &mut Env,
220    vd: VReg,
221    vs2: VReg,
222    vm: bool,
223    vl: BoundedVl<Env::Hart>,
224) where
225    Reg: Register,
226    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
227{
228    let mask_buf = snapshot_mask(env.read_vregs(), vm);
229    let vs2_snap = *env.read_vregs().get(vs2);
230    let mut found_first = false;
231    for i in vl.indices() {
232        if !mask_bit(&mask_buf, i) {
233            continue;
234        }
235        let vs2_bit = mask_bit(&vs2_snap, i);
236        // vmsif: set bits up to *and including* the first set bit; clear elements past it
237        let result = !found_first;
238        if vs2_bit {
239            found_first = true;
240        }
241        write_mask_bit(env.write_vregs(), vd, i, result)
242            .expect("`i < vl <= VLEN`, so the mask bit is within `vd`; qed");
243    }
244    env.mark_vs_dirty();
245}
246
247/// Execute `viota.m`: for each active element `i`, write the popcount of set bits in vs2 at
248/// positions `0..i` (strictly before `i`) as a SEW-wide integer into `vd[i]`.
249///
250/// Per spec §16.8: this instruction honors the source mask; inactive mask elements of vs2 are
251/// treated as zero for the prefix sum. Inactive destination elements follow the mask-agnostic
252/// policy (here implemented as undisturbed, which is a permitted realisation).
253///
254/// If SEW is too narrow to hold the prefix count, the value wraps (truncates to SEW) via
255/// [`VectorRegisterFile::write()`](crate::v::vector_registers::VectorRegisterFile::write); the spec
256/// does not raise an exception for this case.
257///
258/// The caller must reject `vstart != 0` before invocation (spec §16.8 mandatory trap).
259#[inline(always)]
260#[doc(hidden)]
261#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
262pub fn execute_viota<Reg, Env>(env: &mut Env, vd: VRegGroup<Env::Hart>, vs2: VReg, vm: bool)
263where
264    Reg: Register,
265    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
266{
267    let vl = vd.vl();
268    let mask_buf = snapshot_mask(env.read_vregs(), vm);
269    let vs2_snap = *env.read_vregs().get(vs2);
270    // Per spec §16.8: inactive vs2 elements are treated as zero for the prefix sum.
271    // The prefix count advances only when the execution mask is active AND the
272    // corresponding vs2 bit is set.
273    let mut prefix_count = 0u64;
274    for i in vl.indices() {
275        if !mask_bit(&mask_buf, i) {
276            continue;
277        }
278        env.write_vregs()
279            .write(vd, i, prefix_count)
280            .expect("`i < vl` of `vd`; qed");
281        if mask_bit(&vs2_snap, i) {
282            prefix_count += 1;
283        }
284    }
285    env.mark_vs_dirty();
286}
287
288/// Execute `vid.v`: write the element index `i` as a SEW-wide integer into `vd[i]` for each
289/// active element in `0..vl`.
290///
291/// Per spec §16.9: inactive elements are left undisturbed (mask-undisturbed policy).
292///
293/// All register groups must have the same `vl`, which holds for groups created from the same
294/// configuration, nothing is written otherwise.
295#[inline(always)]
296#[doc(hidden)]
297#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
298pub fn execute_vid<Reg, Env>(env: &mut Env, vd: VRegGroup<Env::Hart>, vm: bool)
299where
300    Reg: Register,
301    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
302{
303    let vl = vd.vl();
304    let mask_buf = snapshot_mask(env.read_vregs(), vm);
305    for i in vl.indices() {
306        if !mask_bit(&mask_buf, i) {
307            continue;
308        }
309        env.write_vregs()
310            .write(vd, i, u64::from(i))
311            .expect("`i < vl` of `vd`; qed");
312    }
313    env.mark_vs_dirty();
314}