Skip to main content

ab_riscv_interpreter/v/
vector_registers.rs

1//! Vector registers
2
3mod segment;
4#[cfg(test)]
5mod tests;
6
7use crate::Csrs;
8use crate::v::vector_config::{BoundedVl, VectorConfig};
9use ab_riscv_primitives::prelude::*;
10use core::hint::{assert_unchecked, cold_path};
11use core::marker::Destruct;
12use core::ptr;
13
14pub use segment::{
15    IndexedSegmentElement, IndexedSegmentGroup, SegmentElement, SegmentField, VRegSegmentGroup,
16};
17
18/// `VLENB` of hart configuration as `usize`.
19///
20/// It is the size of registers in [`VectorRegisterFile`], so it also checks that `ELEN` is
21/// supported by Zve* extensions: every environment executing vector instructions stores a register
22/// file.
23pub(crate) const VLENB_USIZE<Hart: VectorHartConfig>: usize = {
24    assert!(
25        matches!(Hart::VECTOR_LENGTHS.elen, Elen::L32 | Elen::L64),
26        "Zve* extensions require `ELEN` of 32 or 64 bits"
27    );
28    Hart::VECTOR_LENGTHS.vlen.bytes() as usize
29};
30/// Element width in bytes as `usize`
31const EEW_BYTES<const EEW: Eew>: usize = EEW.bytes_width() as usize;
32
33/// Register group of elements of width `EEW` that lies within the vector register file.
34///
35/// Created from a [`VectorConfig`] with `EMUL = LMUL * EEW / SEW`, it holds `VLMAX` elements, the
36/// first `vl` of which are the body. This makes the element accesses of
37/// [`VectorRegisterFile::read()`] and friends safe, since the group carries the bounds they check
38/// indices against.
39#[derive(Debug, Clone, Copy)]
40pub struct VRegGroup<Hart>
41where
42    Hart: VectorHartConfig,
43{
44    base: VReg,
45    emul: Vlmul,
46    eew: Eew,
47    // Invariant: `vl <= vlmax` and `vlmax` elements of `eew` starting at `base` end within the
48    // register file
49    vl: BoundedVl<Hart>,
50    vlmax: Vl,
51}
52
53impl<Hart> VRegGroup<Hart>
54where
55    Hart: VectorHartConfig,
56{
57    /// Register group starting at `base` with elements of width `eew` under `config`.
58    ///
59    /// Returns `None` if `eew` exceeds `ELEN`, `EMUL = LMUL * EEW / SEW` is outside `[1/8, 8]` or
60    /// `base` is not aligned to `EMUL`, each of which makes an instruction illegal.
61    #[inline(always)]
62    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
63    pub const fn new(config: VectorConfig<Hart>, base: VReg, eew: Eew) -> Option<Self> {
64        if !Hart::VECTOR_LENGTHS.elen.supports(eew) {
65            cold_path();
66            return None;
67        }
68        let vtype = config.vtype();
69        let emul = vtype.vlmul().emul(eew, vtype.vsew())?;
70        if !base.is_group_aligned(emul.register_count()) {
71            cold_path();
72            return None;
73        }
74
75        let vl = config.vl();
76        let vlmax = config.vlmax();
77        // Both always hold for a legal `EMUL`, but are checked here rather than derived from how
78        // `VectorConfig` and `Vlmul` compute them, since unsafe code relies on the invariant
79        let group_end = u32::from(base.to_bits()) * Hart::VECTOR_LENGTHS.vlen.bytes()
80            + u32::from(vlmax) * u32::from(eew.bytes_width());
81        if u32::from(vl.get()) > u32::from(vlmax)
82            || group_end > 32 * Hart::VECTOR_LENGTHS.vlen.bytes()
83        {
84            cold_path();
85            return None;
86        }
87
88        Some(Self {
89            base,
90            emul,
91            eew,
92            vl,
93            vlmax,
94        })
95    }
96
97    /// Register group of `group_regs` whole registers starting at `base` with elements of width
98    /// `eew`, regardless of `vtype`, like the one of whole register loads, stores and moves.
99    ///
100    /// Both `vl` and `VLMAX` are the number of elements the registers hold. Returns `None` if
101    /// `base` is not aligned to `group_regs` or `eew` is wider than `VLEN`.
102    #[inline(always)]
103    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
104    pub const fn whole_registers(base: VReg, group_regs: VRegGroupSize, eew: Eew) -> Option<Self> {
105        if !base.is_group_aligned(group_regs) {
106            cold_path();
107            return None;
108        }
109        let emul = match group_regs {
110            VRegGroupSize::R1 => Vlmul::M1,
111            VRegGroupSize::R2 => Vlmul::M2,
112            VRegGroupSize::R4 => Vlmul::M4,
113            VRegGroupSize::R8 => Vlmul::M8,
114        };
115        let elements = u32::from(group_regs.get()) * Hart::VECTOR_LENGTHS.vlen.bytes()
116            / u32::from(eew.bytes_width());
117        let vlmax = Vl::new(elements)?;
118        // At most `8 * VLEN / 8`, hence never above `VLEN`
119        let vl = BoundedVl::new(vlmax)?;
120
121        Some(Self {
122            base,
123            emul,
124            eew,
125            vl,
126            vlmax,
127        })
128    }
129
130    /// The same group with `vl` reduced to `vl`, returns `None` if `vl` is above the current one
131    #[inline(always)]
132    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
133    pub const fn with_vl(self, vl: Vl) -> Option<Self> {
134        if u32::from(vl) > u32::from(self.vl.get()) {
135            cold_path();
136            return None;
137        }
138        let vl = BoundedVl::new(vl)?;
139
140        Some(Self { vl, ..self })
141    }
142
143    /// The same group if its `vl` is `vl`, `None` otherwise.
144    ///
145    /// The returned group carries `vl` itself, so that a check of an element index against it is
146    /// trivially the same as a check against `vl` that loops over body elements do anyway, which
147    /// is what makes groups of the same instruction interchangeable for the compiler.
148    #[inline(always)]
149    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
150    pub const fn with_same_vl(self, vl: BoundedVl<Hart>) -> Option<Self> {
151        if self.vl != vl {
152            cold_path();
153            return None;
154        }
155        Some(Self { vl, ..self })
156    }
157
158    /// First register of the group
159    #[inline(always)]
160    pub const fn base(self) -> VReg {
161        self.base
162    }
163
164    /// Effective multiplier `EMUL` of the group
165    #[inline(always)]
166    pub const fn emul(self) -> Vlmul {
167        self.emul
168    }
169
170    /// Number of registers in the group, which is one for fractional `EMUL` too
171    #[inline(always)]
172    pub const fn group_regs(self) -> VRegGroupSize {
173        self.emul.register_count()
174    }
175
176    /// Element width
177    #[inline(always)]
178    pub const fn eew(self) -> Eew {
179        self.eew
180    }
181
182    /// Number of body elements, which is `vl`
183    #[inline(always)]
184    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
185    pub const fn vl(self) -> BoundedVl<Hart> {
186        // SAFETY: Invariant of `VRegGroup`
187        unsafe {
188            assert_unchecked(u32::from(self.vl.get()) <= u32::from(self.vlmax));
189        }
190        self.vl
191    }
192
193    /// Whether `reg` is one of the registers of this group
194    #[inline(always)]
195    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
196    pub const fn contains(self, reg: VReg) -> bool {
197        let (start, reg) = (self.base.to_bits(), reg.to_bits());
198        start <= reg && reg < start + self.group_regs().get()
199    }
200
201    /// Whether the registers of this group and `other` overlap
202    #[inline(always)]
203    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
204    pub const fn overlaps(self, other: Self) -> bool {
205        let (start, other_start) = (self.base.to_bits(), other.base.to_bits());
206        start < other_start + other.group_regs().get()
207            && other_start < start + self.group_regs().get()
208    }
209
210    /// Number of elements the group holds, which is `VLMAX` and at least [`Self::vl()`]
211    #[inline(always)]
212    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
213    pub const fn vlmax(self) -> Vl {
214        // SAFETY: Invariant of `VRegGroup`
215        unsafe {
216            assert_unchecked(u32::from(self.vl.get()) <= u32::from(self.vlmax));
217        }
218        self.vlmax
219    }
220}
221
222/// Vector register file container.
223///
224/// `ELEN` of the hart configuration must be 32 or 64 bits as required by Zve* extensions, which is
225/// checked at compile time.
226#[derive(Debug, Clone, Copy)]
227// Aligned to 128 bytes, which is u32 * 32 registers, the minimum reasonable value to use in most
228// cases
229#[repr(align(128))]
230pub struct VectorRegisterFile<Hart>([[u8; VLENB_USIZE::<Hart>]; 32])
231where
232    Hart: VectorHartConfig;
233
234const impl<Hart> Default for VectorRegisterFile<Hart>
235where
236    Hart: VectorHartConfig,
237{
238    #[inline(always)]
239    fn default() -> Self {
240        Self([[0; _]; _])
241    }
242}
243
244impl<Hart> VectorRegisterFile<Hart>
245where
246    Hart: VectorHartConfig,
247{
248    /// Get reference to a vector register
249    #[inline(always)]
250    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
251    pub const fn get(&self, index: VReg) -> &[u8; VLENB_USIZE::<Hart>] {
252        self.0
253            .get(usize::from(index.to_bits()))
254            .expect("There are exactly 32 vector registers, one for each `VReg`; qed")
255    }
256
257    /// Get mutable reference to a vector register
258    #[inline(always)]
259    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
260    pub const fn get_mut(&mut self, index: VReg) -> &mut [u8; VLENB_USIZE::<Hart>] {
261        self.0
262            .get_mut(usize::from(index.to_bits()))
263            .expect("There are exactly 32 vector registers, one for each `VReg`; qed")
264    }
265
266    /// All vector registers as one contiguous array of bytes.
267    ///
268    /// Register `v` occupies bytes `[v * VLENB, (v + 1) * VLENB)` of the flattened array, so a
269    /// register group is a contiguous range and its elements are at [`Self::element_offset()`].
270    #[inline(always)]
271    pub const fn as_bytes(&self) -> &[[u8; VLENB_USIZE::<Hart>]; 32] {
272        &self.0
273    }
274
275    /// All vector registers as one contiguous mutable array of bytes, see [`Self::as_bytes()`]
276    #[inline(always)]
277    pub const fn as_bytes_mut(&mut self) -> &mut [[u8; VLENB_USIZE::<Hart>]; 32] {
278        &mut self.0
279    }
280
281    /// Byte offset within flattened [`Self::as_bytes()`] of element `elem_i` in the register
282    /// group starting at `base_reg`, with `eew`-wide elements.
283    ///
284    /// Element widths divide `VLENB`, so elements never straddle registers and a register group
285    /// is one contiguous array of elements.
286    #[inline(always)]
287    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
288    pub const fn element_offset<W>(base_reg: VReg, elem_i: u16, eew: W) -> usize
289    where
290        W: [const] Into<Eew> + [const] Destruct,
291    {
292        usize::from(base_reg.to_bits()) * VLENB_USIZE::<Hart>
293            + usize::from(elem_i) * usize::from(eew.into().bytes_width())
294    }
295
296    /// Read body element `elem_i` of `group`, zero-extended.
297    ///
298    /// Returns `None` if `elem_i` is not below [`VRegGroup::vl()`].
299    #[inline(always)]
300    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
301    pub const fn read(&self, group: VRegGroup<Hart>, elem_i: u16) -> Option<u64> {
302        if u32::from(elem_i) >= u32::from(group.vl.get()) {
303            cold_path();
304            return None;
305        }
306        // SAFETY: `elem_i < vl <= vlmax`, and `vlmax` elements of the group lie within the
307        // register file by the invariant of `VRegGroup`
308        Some(unsafe { self.read_element(group.base, elem_i, group.eew) })
309    }
310
311    /// Write the low bits of `value` into body element `elem_i` of `group`.
312    ///
313    /// Returns `None` if `elem_i` is not below [`VRegGroup::vl()`].
314    #[inline(always)]
315    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
316    pub const fn write(&mut self, group: VRegGroup<Hart>, elem_i: u16, value: u64) -> Option<()> {
317        if u32::from(elem_i) >= u32::from(group.vl.get()) {
318            cold_path();
319            return None;
320        }
321        // SAFETY: `elem_i < vl <= vlmax`, and `vlmax` elements of the group lie within the
322        // register file by the invariant of `VRegGroup`
323        unsafe {
324            self.write_element(group.base, elem_i, group.eew, value);
325        }
326        Some(())
327    }
328
329    /// Read any element `elem_i` of `group`, including those past `vl`, zero-extended.
330    ///
331    /// This is for instructions that index a source by data, like `vrgather`. Returns `None` if
332    /// `elem_i` is not below [`VRegGroup::vlmax()`].
333    #[inline(always)]
334    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
335    pub const fn read_up_to_vlmax(&self, group: VRegGroup<Hart>, elem_i: u16) -> Option<u64> {
336        if u32::from(elem_i) >= u32::from(group.vlmax) {
337            cold_path();
338            return None;
339        }
340        // SAFETY: `elem_i < vlmax`, and `vlmax` elements of the group lie within the register
341        // file by the invariant of `VRegGroup`
342        Some(unsafe { self.read_element(group.base, elem_i, group.eew) })
343    }
344
345    /// Copy `count` elements starting at `src_first` of `src` to elements starting at `dst_first`
346    /// of `dst`, like `memmove`, so the two ranges may overlap.
347    ///
348    /// Destination elements must be body elements, source elements may be any elements below
349    /// [`VRegGroup::vlmax()`], like for `vslidedown`. Returns `false` without writing anything if
350    /// either range is out of bounds or the groups have different element widths.
351    #[inline(always)]
352    #[must_use]
353    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
354    pub fn copy_elements(
355        &mut self,
356        dst: VRegGroup<Hart>,
357        dst_first: u16,
358        src: VRegGroup<Hart>,
359        src_first: u16,
360        count: u32,
361    ) -> bool {
362        if dst.eew != src.eew
363            || u32::from(dst_first)
364                .checked_add(count)
365                .is_none_or(|end| end > u32::from(dst.vl.get()))
366            || u32::from(src_first)
367                .checked_add(count)
368                .is_none_or(|end| end > u32::from(src.vlmax))
369        {
370            cold_path();
371            return false;
372        }
373        let dst_offset = Self::element_offset(dst.base, dst_first, dst.eew);
374        let src_offset = Self::element_offset(src.base, src_first, src.eew);
375        let Ok(count) = usize::try_from(count) else {
376            cold_path();
377            return false;
378        };
379        let len = count * usize::from(dst.eew.bytes_width());
380        let bytes = self.as_bytes_mut().as_flattened_mut().as_mut_ptr();
381        // SAFETY: Both element ranges were checked above to lie within `vlmax` elements of their
382        // groups, which lie within the register file by the invariant of `VRegGroup`. Overlapping
383        // ranges are fine for `ptr::copy()`, and both pointers derive from the same mutable one.
384        unsafe {
385            ptr::copy(bytes.byte_add(src_offset), bytes.byte_add(dst_offset), len);
386        }
387        true
388    }
389
390    /// Bytes of `count` body elements starting at `first` of `group`.
391    ///
392    /// Returns `None` if the range is not within [`VRegGroup::vl()`].
393    #[inline(always)]
394    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
395    pub fn elements_bytes(&self, group: VRegGroup<Hart>, first: u16, count: u32) -> Option<&[u8]> {
396        if u32::from(first)
397            .checked_add(count)
398            .is_none_or(|end| end > u32::from(group.vl().get()))
399        {
400            cold_path();
401            return None;
402        }
403        let offset = Self::element_offset(group.base, first, group.eew);
404        let len = usize::try_from(count).ok()? * usize::from(group.eew.bytes_width());
405        // SAFETY: The element range was checked above to lie within `vl` elements of the group,
406        // which lie within the register file by the invariant of `VRegGroup`
407        Some(unsafe {
408            self.as_bytes()
409                .as_flattened()
410                .get_unchecked(offset..offset + len)
411        })
412    }
413
414    /// Mutable bytes of `count` body elements starting at `first` of `group`.
415    ///
416    /// Returns `None` if the range is not within [`VRegGroup::vl()`].
417    #[inline(always)]
418    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
419    pub fn elements_bytes_mut(
420        &mut self,
421        group: VRegGroup<Hart>,
422        first: u16,
423        count: u32,
424    ) -> Option<&mut [u8]> {
425        if u32::from(first)
426            .checked_add(count)
427            .is_none_or(|end| end > u32::from(group.vl().get()))
428        {
429            cold_path();
430            return None;
431        }
432        let offset = Self::element_offset(group.base, first, group.eew);
433        let len = usize::try_from(count).ok()? * usize::from(group.eew.bytes_width());
434        // SAFETY: The element range was checked above to lie within `vl` elements of the group,
435        // which lie within the register file by the invariant of `VRegGroup`
436        Some(unsafe {
437            self.as_bytes_mut()
438                .as_flattened_mut()
439                .get_unchecked_mut(offset..offset + len)
440        })
441    }
442
443    /// Read element 0 of register `reg` with width `eew`, zero-extended.
444    ///
445    /// This is the scalar operand of reductions and scalar moves, which is a single register
446    /// regardless of `LMUL`. Returns `None` if `eew` is wider than `VLEN`, which never happens for
447    /// an element width that doesn't exceed `ELEN`.
448    #[inline(always)]
449    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
450    pub const fn read_first(&self, reg: VReg, eew: Eew) -> Option<u64> {
451        if u32::from(eew.bytes_width()) > Hart::VECTOR_LENGTHS.vlen.bytes() {
452            cold_path();
453            return None;
454        }
455        // SAFETY: Element 0 of `reg` occupies the first `eew.bytes_width() <= VLEN.bytes()` bytes
456        // of the register, which is within the register file
457        Some(unsafe { self.read_element(reg, 0, eew) })
458    }
459
460    /// Write the low bits of `value` into element 0 of register `reg` with width `eew`.
461    ///
462    /// Returns `None` if `eew` is wider than `VLEN`, see [`Self::read_first()`].
463    #[inline(always)]
464    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
465    pub const fn write_first(&mut self, reg: VReg, eew: Eew, value: u64) -> Option<()> {
466        if u32::from(eew.bytes_width()) > Hart::VECTOR_LENGTHS.vlen.bytes() {
467            cold_path();
468            return None;
469        }
470        // SAFETY: Element 0 of `reg` occupies the first `eew.bytes_width() <= VLEN.bytes()` bytes
471        // of the register, which is within the register file
472        unsafe {
473            self.write_element(reg, 0, eew, value);
474        }
475        Some(())
476    }
477
478    /// [`Self::read()`] with the element width known at compile time, which makes the access a
479    /// fixed-size load.
480    ///
481    /// Returns `None` if `EEW` is not the element width of `group` or `elem_i` is not below
482    /// [`VRegGroup::vl()`].
483    #[inline(always)]
484    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
485    pub const fn read_const<const EEW: Eew>(
486        &self,
487        group: VRegGroup<Hart>,
488        elem_i: u16,
489    ) -> Option<u64> {
490        if group.eew != EEW || u32::from(elem_i) >= u32::from(group.vl.get()) {
491            cold_path();
492            return None;
493        }
494        // SAFETY: `elem_i < vl <= vlmax`, and `vlmax` elements of `EEW` of the group lie within
495        // the register file by the invariant of `VRegGroup`
496        Some(unsafe { self.read_element_const::<EEW>(group.base, elem_i) })
497    }
498
499    /// [`Self::write()`] with the element width known at compile time, which makes the access a
500    /// fixed-size store.
501    ///
502    /// Returns `None` if `EEW` is not the element width of `group` or `elem_i` is not below
503    /// [`VRegGroup::vl()`].
504    #[inline(always)]
505    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
506    pub const fn write_const<const EEW: Eew>(
507        &mut self,
508        group: VRegGroup<Hart>,
509        elem_i: u16,
510        value: u64,
511    ) -> Option<()> {
512        if group.eew != EEW || u32::from(elem_i) >= u32::from(group.vl.get()) {
513            cold_path();
514            return None;
515        }
516        // SAFETY: `elem_i < vl <= vlmax`, and `vlmax` elements of `EEW` of the group lie within
517        // the register file by the invariant of `VRegGroup`
518        unsafe {
519            self.write_element_const::<EEW>(group.base, elem_i, value);
520        }
521        Some(())
522    }
523
524    /// Read element `elem_i` of the register group starting at `base_reg`, with `eew`-wide
525    /// elements, zero-extended.
526    ///
527    /// # Safety
528    /// The element must lie within the register file, which holds for any `elem_i < vl` of a
529    /// register group `[base_reg, base_reg + group_regs)` that ends within the register file,
530    /// since `vl <= group_regs * VLENB / eew.bytes_width()`.
531    #[inline(always)]
532    const unsafe fn read_element<W>(&self, base_reg: VReg, elem_i: u16, eew: W) -> u64
533    where
534        W: [const] Into<Eew> + [const] Destruct,
535    {
536        // SAFETY: Guaranteed by the caller's precondition
537        unsafe {
538            match eew.into() {
539                Eew::E8 => self.read_element_const::<{ Eew::E8 }>(base_reg, elem_i),
540                Eew::E16 => self.read_element_const::<{ Eew::E16 }>(base_reg, elem_i),
541                Eew::E32 => self.read_element_const::<{ Eew::E32 }>(base_reg, elem_i),
542                Eew::E64 => self.read_element_const::<{ Eew::E64 }>(base_reg, elem_i),
543            }
544        }
545    }
546
547    /// Write the low `eew.bytes_width()` bytes of `value` into element `elem_i` of the register
548    /// group starting at `base_reg`, with `eew`-wide elements.
549    ///
550    /// # Safety
551    /// Same as [`Self::read_element()`]
552    #[inline(always)]
553    const unsafe fn write_element<W>(&mut self, base_reg: VReg, elem_i: u16, eew: W, value: u64)
554    where
555        W: [const] Into<Eew> + [const] Destruct,
556    {
557        // SAFETY: Guaranteed by the caller's precondition
558        unsafe {
559            match eew.into() {
560                Eew::E8 => self.write_element_const::<{ Eew::E8 }>(base_reg, elem_i, value),
561                Eew::E16 => self.write_element_const::<{ Eew::E16 }>(base_reg, elem_i, value),
562                Eew::E32 => self.write_element_const::<{ Eew::E32 }>(base_reg, elem_i, value),
563                Eew::E64 => self.write_element_const::<{ Eew::E64 }>(base_reg, elem_i, value),
564            }
565        }
566    }
567
568    /// [`Self::read_element()`] with the element width known at compile time, which makes the
569    /// access a fixed-size load
570    ///
571    /// # Safety
572    /// Same as [`Self::read_element()`]
573    #[inline(always)]
574    const unsafe fn read_element_const<const EEW: Eew>(&self, base_reg: VReg, elem_i: u16) -> u64 {
575        let offset = Self::element_offset(base_reg, elem_i, EEW);
576        // SAFETY: `offset + EEW.bytes_width() <= 32 * VLENB` by the caller's precondition
577        let element = unsafe {
578            self.as_bytes()
579                .as_flattened()
580                .get_unchecked(offset..)
581                .first_chunk::<{ EEW_BYTES::<EEW> }>()
582                .unwrap_unchecked()
583        };
584        let mut bytes = 0u64.to_le_bytes();
585        if let Some((low, _)) = bytes.split_first_chunk_mut::<{ EEW_BYTES::<EEW> }>() {
586            *low = *element;
587        }
588        u64::from_le_bytes(bytes)
589    }
590
591    /// [`Self::write_element()`] with the element width known at compile time, which makes the
592    /// access a fixed-size store
593    ///
594    /// # Safety
595    /// Same as [`Self::read_element()`]
596    #[inline(always)]
597    const unsafe fn write_element_const<const EEW: Eew>(
598        &mut self,
599        base_reg: VReg,
600        elem_i: u16,
601        value: u64,
602    ) {
603        let offset = Self::element_offset(base_reg, elem_i, EEW);
604        // SAFETY: `offset + EEW.bytes_width() <= 32 * VLENB` by the caller's precondition
605        let element = unsafe {
606            self.as_bytes_mut()
607                .as_flattened_mut()
608                .get_unchecked_mut(offset..)
609                .first_chunk_mut::<{ EEW_BYTES::<EEW> }>()
610                .unwrap_unchecked()
611        };
612        if let Some((low, _)) = value
613            .to_le_bytes()
614            .split_first_chunk::<{ EEW_BYTES::<EEW> }>()
615        {
616            *element = *low;
617        }
618    }
619}
620
621/// General purpose register type of the hart configuration of vector registers
622type HartReg<Env> = <<Env as VectorRegisters>::Hart as HartConfig>::Reg;
623/// Raw value type of [`HartReg`]
624type HartRegType<Env> = <HartReg<Env> as Register>::Type;
625
626/// Vector register state.
627///
628/// This trait contains only methods that implementations genuinely need to provide. Derived
629/// accessors for simpler CSRs are in [`VectorRegistersExt`].
630///
631/// Note that due to Rust type system limitations, you should use [`VectorRegistersExt`] in trait
632/// bounds instead of this trait directly or else the solver will fail.
633pub const trait VectorRegisters {
634    /// Hart configuration, which determines vector lengths
635    type Hart: [const] VectorHartConfig;
636
637    /// Read the vector register file
638    fn read_vregs(&self) -> &VectorRegisterFile<Self::Hart>;
639
640    /// Mutable access to the vector register file
641    fn write_vregs(&mut self) -> &mut VectorRegisterFile<Self::Hart>;
642
643    /// Check whether vector instructions are currently permitted.
644    ///
645    /// Returns `false` when `mstatus.VS == Off` (or equivalent like `sstatus`/`vstatus`). In
646    /// environments without these status registers, returns `true` always.
647    fn vector_instructions_allowed(&self) -> bool;
648
649    /// Mark the vector state as dirty.
650    ///
651    /// Must set VS to Dirty in `mstatus` (and `sstatus`/`vsstatus` shadows) when those registers
652    /// exist. No-op otherwise.
653    fn mark_vs_dirty(&mut self);
654}
655
656/// Derived convenience accessors for vector CSRs that are simple read/write fields (vstart, vxrm,
657/// vxsat, vcsr).
658///
659/// Intended for types that implement both [`VectorRegisters`] and [`Csrs`].
660///
661/// NOTE: While the default methods implemented via the [`Csrs`] trait are correct, custom
662/// higher-performance implementations are often possible by overriding them and, for example,
663/// caching various CSRs as separate pre-decoded values rather than going through a generic code
664/// path with XLEN-sized raw CSR values during reads.
665pub const trait VectorRegistersExt
666where
667    Self: [const] VectorRegisters + [const] Csrs<HartReg<Self>>,
668{
669    /// Initialize the vector state to the recommended default configuration.
670    ///
671    /// Per spec: `vtype.vill` = 1, remaining `vtype` bits = `0`, `vl` = 0.
672    /// `vstart`, `vxrm`, `vxsat` may have arbitrary values at reset but are zeroed here for
673    /// deterministic behavior.
674    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
675    fn initialize_vector_state(&mut self) {
676        self.set_vector_config(None);
677        self.set_vstart(Vstart::ZERO);
678        self.set_vxrm(Vxrm::default());
679        self.set_vxsat(false);
680    }
681
682    /// Get current `vstart`
683    #[inline(always)]
684    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
685    fn vstart(&self) -> Vstart {
686        let raw = self
687            .read_csr(VectorCsr::Vstart.to_csr_index())
688            .unwrap_or_default()
689            .as_u64();
690        Vstart::from(raw as u16)
691    }
692
693    /// Set `vstart`.
694    ///
695    /// The default implementation ignores writes to uninitialized CSR in release mode and panics in
696    /// debug.
697    #[inline(always)]
698    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
699    fn set_vstart(&mut self, vstart: Vstart) {
700        let result = self.write_csr(
701            VectorCsr::Vstart.to_csr_index(),
702            HartRegType::<Self>::from(u16::from(vstart)),
703        );
704        debug_assert!(
705            result.is_ok(),
706            "Implementation must initialize `vstart` CSR"
707        );
708    }
709
710    /// Reset `vstart` to zero.
711    ///
712    /// Per spec, all vector instructions reset `vstart` to zero at the end of execution.
713    #[inline(always)]
714    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
715    fn reset_vstart(&mut self) {
716        self.set_vstart(Vstart::ZERO);
717    }
718
719    /// Get `vxsat` (single bit)
720    #[inline(always)]
721    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
722    fn vxsat(&self) -> bool {
723        let raw = self
724            .read_csr(VectorCsr::Vxsat.to_csr_index())
725            .unwrap_or_default()
726            .as_u64();
727        (raw & 1) == 1
728    }
729
730    /// Set `vxsat`.
731    ///
732    /// The default implementation ignores writes to uninitialized CSR in release mode and panics in
733    /// debug.
734    #[inline(always)]
735    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
736    fn set_vxsat(&mut self, vxsat: bool) {
737        let masked = HartRegType::<Self>::from(u8::from(vxsat));
738        let result = self.write_csr(VectorCsr::Vxsat.to_csr_index(), masked);
739        debug_assert!(result.is_ok(), "Implementation must initialize `vxsat` CSR");
740        // Mirror `vxsat` into `vcsr[0]`, preserving `vcsr[2:1]` (`vxrm`)
741        let old_vcsr = self
742            .read_csr(VectorCsr::Vcsr.to_csr_index())
743            .unwrap_or_default();
744        let new_vcsr = (old_vcsr & !HartRegType::<Self>::from(1u8)) | masked;
745        let result = self.write_csr(VectorCsr::Vcsr.to_csr_index(), new_vcsr);
746        debug_assert!(result.is_ok(), "Implementation must initialize `vcsr` CSR");
747    }
748
749    /// Get `vxrm`
750    #[inline(always)]
751    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
752    fn vxrm(&self) -> Vxrm {
753        let raw = self
754            .read_csr(VectorCsr::Vxrm.to_csr_index())
755            .unwrap_or_default()
756            .as_u64();
757        Vxrm::from_bits(raw as u8)
758    }
759
760    /// Set `vxrm`.
761    ///
762    /// The default implementation ignores writes to uninitialized CSR in release mode and panics in
763    /// debug.
764    #[inline(always)]
765    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
766    fn set_vxrm(&mut self, vxrm: Vxrm) {
767        let masked = HartRegType::<Self>::from(vxrm.to_bits());
768        let result = self.write_csr(VectorCsr::Vxrm.to_csr_index(), masked);
769        debug_assert!(result.is_ok(), "Implementation must initialize `vxrm` CSR");
770        // Mirror `vxrm` into `vcsr[2:1]`, preserving `vcsr[0]` (`vxsat`)
771        let old_vcsr = self
772            .read_csr(VectorCsr::Vcsr.to_csr_index())
773            .unwrap_or_default();
774        let new_vcsr = (old_vcsr & !HartRegType::<Self>::from(0b110u8)) | (masked << 1u8);
775        let result = self.write_csr(VectorCsr::Vcsr.to_csr_index(), new_vcsr);
776        debug_assert!(result.is_ok(), "Implementation must initialize `vcsr` CSR");
777    }
778
779    /// Get the current vector configuration, `None` when `vill` is set.
780    ///
781    /// This is the only source of `vtype` and `vl` for instructions. An instruction reads it once
782    /// and derives everything from that single value, so even an implementation that returns
783    /// different configurations from different calls can't make it combine a `vl` with a `vtype`
784    /// it does not belong to.
785    ///
786    /// The default implementation decodes the raw `vtype` and `vl` CSRs and treats an inconsistent
787    /// pair as `vill`.
788    #[inline(always)]
789    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
790    fn vector_config(&self) -> Option<VectorConfig<Self::Hart>> {
791        let vtype = self.read_csr(VectorCsr::Vtype.to_csr_index()).ok()?;
792        let vl = self.read_csr(VectorCsr::Vl.to_csr_index()).ok()?;
793        VectorConfig::from_raw::<HartReg<Self>>(vtype, vl)
794    }
795
796    /// Set the vector configuration, `None` sets `vill` (and `vl` to zero).
797    ///
798    /// The implementation must also make the raw `vtype` and `vl` values available for reads via
799    /// Zicsr (writes via Zicsr are not allowed).
800    ///
801    /// The default implementation ignores writes to uninitialized CSR in release mode and panics in
802    /// debug.
803    #[inline(always)]
804    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
805    fn set_vector_config(&mut self, vector_config: Option<VectorConfig<Self::Hart>>) {
806        let (vtype_raw, vl_raw) = if let Some(vector_config) = vector_config {
807            (
808                vector_config.vtype().to_raw::<HartReg<Self>>(),
809                HartRegType::<Self>::from(u32::from(vector_config.vl().get())),
810            )
811        } else {
812            (
813                Vtype::<Self::Hart>::illegal_raw::<HartReg<Self>>(),
814                HartRegType::<Self>::from(0u8),
815            )
816        };
817
818        let result = self.write_csr(VectorCsr::Vtype.to_csr_index(), vtype_raw);
819        debug_assert!(result.is_ok(), "Implementation must initialize `vtype` CSR");
820        let result = self.write_csr(VectorCsr::Vl.to_csr_index(), vl_raw);
821        debug_assert!(result.is_ok(), "Implementation must initialize `vl` CSR");
822    }
823}
824
825// Convenience for threaded execution
826const impl<T> VectorRegisters for &mut T
827where
828    T: [const] VectorRegisters,
829{
830    type Hart = T::Hart;
831
832    #[inline(always)]
833    fn read_vregs(&self) -> &VectorRegisterFile<Self::Hart> {
834        T::read_vregs(self)
835    }
836
837    #[inline(always)]
838    fn write_vregs(&mut self) -> &mut VectorRegisterFile<Self::Hart> {
839        T::write_vregs(self)
840    }
841
842    #[inline(always)]
843    fn vector_instructions_allowed(&self) -> bool {
844        T::vector_instructions_allowed(self)
845    }
846
847    #[inline(always)]
848    fn mark_vs_dirty(&mut self) {
849        T::mark_vs_dirty(self);
850    }
851}
852
853// Convenience for threaded execution.
854//
855// Every method is forwarded explicitly rather than left to `VectorRegistersExt`'s defaults: those
856// go through `Csrs::write_csr()`, which the blanket `Csrs for &mut T` impl also forwards to `T`,
857// so an empty impl here would silently observe `T`'s overrides for some accessors and the trait
858// defaults for others
859const impl<T> VectorRegistersExt for &mut T
860where
861    T: [const] VectorRegistersExt,
862{
863    #[inline(always)]
864    fn initialize_vector_state(&mut self) {
865        T::initialize_vector_state(self);
866    }
867
868    #[inline(always)]
869    fn vstart(&self) -> Vstart {
870        T::vstart(self)
871    }
872
873    #[inline(always)]
874    fn set_vstart(&mut self, vstart: Vstart) {
875        T::set_vstart(self, vstart);
876    }
877
878    #[inline(always)]
879    fn reset_vstart(&mut self) {
880        T::reset_vstart(self);
881    }
882
883    #[inline(always)]
884    fn vxsat(&self) -> bool {
885        T::vxsat(self)
886    }
887
888    #[inline(always)]
889    fn set_vxsat(&mut self, vxsat: bool) {
890        T::set_vxsat(self, vxsat);
891    }
892
893    #[inline(always)]
894    fn vxrm(&self) -> Vxrm {
895        T::vxrm(self)
896    }
897
898    #[inline(always)]
899    fn set_vxrm(&mut self, vxrm: Vxrm) {
900        T::set_vxrm(self, vxrm);
901    }
902
903    #[inline(always)]
904    fn vector_config(&self) -> Option<VectorConfig<Self::Hart>> {
905        T::vector_config(self)
906    }
907
908    #[inline(always)]
909    fn set_vector_config(&mut self, vector_config: Option<VectorConfig<Self::Hart>>) {
910        T::set_vector_config(self, vector_config);
911    }
912}