Skip to main content

ab_riscv_primitives/instructions/
v.rs

1//! V extension
2
3pub mod zvexx;
4
5use crate::registers::general_purpose::{RegType, Register};
6use core::fmt;
7use core::hint::cold_path;
8
9/// `mstatus.VS` / `sstatus.VS` / `vsstatus.VS` field encoding.
10///
11/// Context status for the vector extension, analogous to `mstatus.FS`.
12/// Located at bits `[10:9]` in the respective status registers.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum VsStatus {
16    /// Vector unit is off; any vector instruction or CSR access raises illegal instruction
17    Off = 0,
18    /// Vector state is known to be in its initial state
19    Initial = 1,
20    /// Vector state is potentially modified but matches the last saved state
21    Clean = 2,
22    /// Vector state has been modified since the last save
23    Dirty = 3,
24}
25
26impl VsStatus {
27    /// Decode from a 2-bit field value
28    #[inline(always)]
29    pub const fn from_bits(bits: u8) -> Self {
30        match bits & 0b11 {
31            0 => Self::Off,
32            1 => Self::Initial,
33            2 => Self::Clean,
34            _ => Self::Dirty,
35        }
36    }
37
38    /// Encode to a 2-bit field value
39    #[inline(always)]
40    pub const fn to_bits(self) -> u8 {
41        self as u8
42    }
43}
44
45/// Vector length multiplier (LMUL) setting.
46///
47/// Encoded in `vtype[2:0]` as a signed 3-bit value.
48/// `LMUL = 2^vlmul` where `vlmul` is sign-extended. Positive values give integer multipliers,
49/// negative values give fractional.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[repr(u8)]
52pub enum Vlmul {
53    /// LMUL = 1 (`vlmul` encoding 0b000)
54    M1 = 0b000,
55    /// LMUL = 2 (`vlmul` encoding 0b001)
56    M2 = 0b001,
57    /// LMUL = 4 (`vlmul` encoding 0b010)
58    M4 = 0b010,
59    /// LMUL = 8 (`vlmul` encoding 0b011)
60    M8 = 0b011,
61    /// LMUL = 1/8 (`vlmul` encoding 0b101)
62    Mf8 = 0b101,
63    /// LMUL = 1/4 (`vlmul` encoding 0b110)
64    Mf4 = 0b110,
65    /// LMUL = 1/2 (`vlmul` encoding 0b111)
66    Mf2 = 0b111,
67}
68
69impl Vlmul {
70    /// Decode from the 3-bit `vlmul` field. Returns `None` for reserved encoding 0b100.
71    #[inline(always)]
72    pub const fn from_bits(bits: u8) -> Option<Self> {
73        match bits & 0b111 {
74            0b000 => Some(Self::M1),
75            0b001 => Some(Self::M2),
76            0b010 => Some(Self::M4),
77            0b011 => Some(Self::M8),
78            0b101 => Some(Self::Mf8),
79            0b110 => Some(Self::Mf4),
80            0b111 => Some(Self::Mf2),
81            _ => None,
82        }
83    }
84
85    /// Encode to the 3-bit `vlmul` field
86    #[inline(always)]
87    pub const fn to_bits(self) -> u8 {
88        self as u8
89    }
90
91    /// Compute `VLMAX = LMUL * VLEN / SEW`.
92    ///
93    /// For fractional LMUL, this is `VLEN / (SEW * denominator)`.
94    /// Returns 0 when the result would be less than 1 (insufficient bits).
95    #[inline(always)]
96    pub const fn vlmax<const VLEN: u32>(self, sew: Vsew) -> u32 {
97        let sew_bits = u32::from(sew.bits_width());
98        match self {
99            Self::M1 => VLEN / sew_bits,
100            Self::M2 => (VLEN * 2) / sew_bits,
101            Self::M4 => (VLEN * 4) / sew_bits,
102            Self::M8 => (VLEN * 8) / sew_bits,
103            Self::Mf2 => VLEN / (sew_bits * 2),
104            Self::Mf4 => VLEN / (sew_bits * 4),
105            Self::Mf8 => VLEN / (sew_bits * 8),
106        }
107    }
108
109    /// Number of vector registers occupied by one register group at this `LMUL`.
110    ///
111    /// Fractional `LMUL` values (`Mf2`, `Mf4`, `Mf8`) each occupy exactly 1 register.
112    /// Integer `LMUL` values occupy 1, 2, 4, or 8 registers respectively.
113    #[inline(always)]
114    pub const fn register_count(self) -> u8 {
115        match self {
116            Self::Mf8 | Self::Mf4 | Self::Mf2 | Self::M1 => 1,
117            Self::M2 => 2,
118            Self::M4 => 4,
119            Self::M8 => 8,
120        }
121    }
122
123    /// LMUL as a `(numerator, denominator)` fraction where `LMUL = num / den`.
124    ///
125    /// Both values are powers of two with exactly one equal to `1`. Useful for computing
126    /// `EMUL = (EEW / SEW) * LMUL` without floating-point arithmetic.
127    #[inline(always)]
128    pub const fn as_fraction(self) -> (u8, u8) {
129        match self {
130            Self::Mf8 => (1, 8),
131            Self::Mf4 => (1, 4),
132            Self::Mf2 => (1, 2),
133            Self::M1 => (1, 1),
134            Self::M2 => (2, 1),
135            Self::M4 => (4, 1),
136            Self::M8 => (8, 1),
137        }
138    }
139
140    /// Compute `EMUL` for an indexed load: `EMUL = (index_eew / sew) * LMUL`.
141    ///
142    /// Returns the register count for the index register group, or `None` when `EMUL` falls
143    /// outside the legal range `[1/8, 8]`.
144    #[inline(always)]
145    pub const fn index_register_count(self, index_eew: Eew, sew: Vsew) -> Option<u8> {
146        let (lmul_num, lmul_den) = self.as_fraction();
147        let num = u16::from(index_eew.bits_width()) * u16::from(lmul_num);
148        let den = u16::from(sew.bits_width()) * u16::from(lmul_den);
149        // Both are products of powers of two; GCD equals the smaller value.
150        let g = if num < den { num } else { den };
151        let (n, d) = (num / g, den / g);
152        // Legal EMUL fractions: 1/8, 1/4, 1/2, 1, 2, 4, 8
153        let legal = matches!(
154            (n, d),
155            (1, 8) | (1, 4) | (1, 2) | (1, 1) | (2, 1) | (4, 1) | (8, 1)
156        );
157        if !legal {
158            cold_path();
159            return None;
160        }
161        // Register count is max(1, n/d) = n when d==1, else 1
162        Some(if d > 1 { 1 } else { n as u8 })
163    }
164
165    /// Compute EMUL for a data operand of a memory instruction with a given effective element
166    /// width: `EMUL = (eew / sew) * LMUL`.
167    ///
168    /// Mathematically identical to [`Self::index_register_count`], but exposed under a distinct
169    /// name for call sites where the EEW describes the *data* being loaded or stored rather than an
170    /// index. Keeping the two entry points separate avoids accidental semantic drift if
171    /// one of them is later specialised.
172    ///
173    /// Returns `None` when the resulting EMUL falls outside the legal range `[1/8, 8]`.
174    #[inline(always)]
175    pub const fn data_register_count(self, eew: Eew, sew: Vsew) -> Option<u8> {
176        self.index_register_count(eew, sew)
177    }
178}
179
180impl fmt::Display for Vlmul {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        match self {
183            Self::M1 => write!(f, "m1"),
184            Self::M2 => write!(f, "m2"),
185            Self::M4 => write!(f, "m4"),
186            Self::M8 => write!(f, "m8"),
187            Self::Mf8 => write!(f, "mf8"),
188            Self::Mf4 => write!(f, "mf4"),
189            Self::Mf2 => write!(f, "mf2"),
190        }
191    }
192}
193
194/// Factor by which Vsew width is divided
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196#[repr(u8)]
197pub enum VsewFactor {
198    /// Divide width by 2
199    F2 = 2,
200    /// Divide width by 4
201    F4 = 4,
202    /// Divide width by 8
203    F8 = 8,
204}
205
206impl VsewFactor {
207    /// Return the numeric divisor used to scale down a [`Vsew`] bit-width
208    #[inline(always)]
209    pub const fn factor(self) -> u8 {
210        self as u8
211    }
212}
213
214/// Selected element width (SEW).
215///
216/// Encoded in `vtype[5:3]` as `vsew`. `SEW = 8 * 2^vsew`.
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218#[repr(u8)]
219pub enum Vsew {
220    /// SEW = 8 bits (vsew = 0b000)
221    E8 = 8,
222    /// SEW = 16 bits (vsew = 0b001)
223    E16 = 16,
224    /// SEW = 32 bits (vsew = 0b010)
225    E32 = 32,
226    /// SEW = 64 bits (vsew = 0b011)
227    E64 = 64,
228}
229
230impl Vsew {
231    /// Decode from the 3-bit vsew field. Returns `None` for reserved encodings.
232    #[inline(always)]
233    pub const fn from_bits(bits: u8) -> Option<Self> {
234        match bits & 0b111 {
235            0b000 => Some(Self::E8),
236            0b001 => Some(Self::E16),
237            0b010 => Some(Self::E32),
238            0b011 => Some(Self::E64),
239            _ => {
240                cold_path();
241                None
242            }
243        }
244    }
245
246    /// Encode to the 3-bit vsew field
247    #[inline(always)]
248    pub const fn to_bits(self) -> u8 {
249        match self {
250            Vsew::E8 => 0b000,
251            Vsew::E16 => 0b001,
252            Vsew::E32 => 0b010,
253            Vsew::E64 => 0b011,
254        }
255    }
256
257    /// Get the double element width, if available
258    #[inline(always)]
259    pub const fn double_width(self) -> Option<Self> {
260        match self {
261            Self::E8 => Some(Self::E16),
262            Self::E16 => Some(Self::E32),
263            Self::E32 => Some(Self::E64),
264            Self::E64 => {
265                cold_path();
266                None
267            }
268        }
269    }
270
271    /// Divide Vsew width by a given factor
272    #[inline(always)]
273    pub const fn divide_by_factor(self, factor: VsewFactor) -> Option<Self> {
274        let Some(divide_by_factor) = self.bits_width().div_exact(factor.factor()) else {
275            cold_path();
276            return None;
277        };
278        match divide_by_factor {
279            8 => Some(Self::E8),
280            16 => Some(Self::E16),
281            32 => Some(Self::E32),
282            _ => {
283                cold_path();
284                None
285            }
286        }
287    }
288
289    /// Element width in bits
290    #[inline(always)]
291    pub const fn bits_width(self) -> u8 {
292        match self {
293            Self::E8 => 8,
294            Self::E16 => 16,
295            Self::E32 => 32,
296            Self::E64 => 64,
297        }
298    }
299
300    /// Element width in bytes
301    #[inline(always)]
302    pub const fn bytes_width(self) -> u8 {
303        match self {
304            Self::E8 => 1,
305            Self::E16 => 2,
306            Self::E32 => 4,
307            Self::E64 => 8,
308        }
309    }
310
311    /// Convert to the corresponding `Eew` variant.
312    ///
313    /// Every valid `Vsew` value has a directly corresponding `Eew` value because both
314    /// enumerate the same set of widths (8/16/32/64 bits). The conversion is always
315    /// successful.
316    #[inline(always)]
317    pub const fn as_eew(self) -> Eew {
318        match self {
319            Self::E8 => Eew::E8,
320            Self::E16 => Eew::E16,
321            Self::E32 => Eew::E32,
322            Self::E64 => Eew::E64,
323        }
324    }
325}
326
327impl fmt::Display for Vsew {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        match self {
330            Self::E8 => write!(f, "e8"),
331            Self::E16 => write!(f, "e16"),
332            Self::E32 => write!(f, "e32"),
333            Self::E64 => write!(f, "e64"),
334        }
335    }
336}
337
338/// Effective element width for vector memory operations
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340#[repr(u8)]
341pub enum Eew {
342    /// 8-bit elements
343    E8 = 1,
344    /// 16-bit elements
345    E16 = 2,
346    /// 32-bit elements
347    E32 = 4,
348    /// 64-bit elements
349    E64 = 8,
350}
351
352impl Eew {
353    /// Max element width in bytes
354    pub const MAX_BYTES: u8 = Self::E64.bytes_width();
355
356    /// Decode the width field into an element width
357    #[inline(always)]
358    pub const fn from_width(width: u8) -> Option<Self> {
359        match width {
360            0b000 => Some(Self::E8),
361            0b101 => Some(Self::E16),
362            0b110 => Some(Self::E32),
363            0b111 => Some(Self::E64),
364            _ => {
365                cold_path();
366                None
367            }
368        }
369    }
370
371    /// Encode to the 3-bit Eew field
372    #[inline(always)]
373    pub const fn to_bits(self) -> u8 {
374        match self {
375            Eew::E8 => 0b000,
376            Eew::E16 => 0b101,
377            Eew::E32 => 0b110,
378            Eew::E64 => 0b111,
379        }
380    }
381
382    /// Element width in bits
383    #[inline(always)]
384    pub const fn bits_width(self) -> u8 {
385        match self {
386            Self::E8 => 8,
387            Self::E16 => 16,
388            Self::E32 => 32,
389            Self::E64 => 64,
390        }
391    }
392
393    /// Element width in bytes.
394    ///
395    /// Guaranteed to be `<= Self::MAX_BYTES`.
396    #[inline(always)]
397    pub const fn bytes_width(self) -> u8 {
398        match self {
399            Self::E8 => 1,
400            Self::E16 => 2,
401            Self::E32 => 4,
402            Self::E64 => 8,
403        }
404    }
405}
406
407impl fmt::Display for Eew {
408    #[inline]
409    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410        fmt::Display::fmt(&self.bits_width(), f)
411    }
412}
413
414/// Vector fixed-point rounding mode.
415///
416/// Encoded in the `vxrm` CSR bits `[1:0]` and mirrored in `vcsr[2:1]`.
417#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
418#[repr(u8)]
419pub enum Vxrm {
420    /// Round-to-nearest-up (rnu)
421    #[default]
422    Rnu = 0b00,
423    /// Round-to-nearest-even (rne)
424    Rne = 0b01,
425    /// Round-down / truncate (rdn)
426    Rdn = 0b10,
427    /// Round-to-odd (rod)
428    Rod = 0b11,
429}
430
431impl Vxrm {
432    /// Decode from a 2-bit field
433    #[inline(always)]
434    pub const fn from_bits(bits: u8) -> Self {
435        match bits & 0b11 {
436            0b00 => Self::Rnu,
437            0b01 => Self::Rne,
438            0b10 => Self::Rdn,
439            _ => Self::Rod,
440        }
441    }
442
443    /// Encode to a 2-bit field
444    #[inline(always)]
445    pub const fn to_bits(self) -> u8 {
446        self as u8
447    }
448}
449
450/// Decoded `vtype` register contents.
451///
452/// The vtype CSR controls the interpretation of the vector register file: element width, register
453/// grouping, and tail/mask agnostic policies.
454///
455/// The raw encoding is XLEN-dependent (vill is at bit XLEN-1), but this decoded form is
456/// XLEN-independent.
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub struct Vtype<const ELEN: u32, const VLEN: u32> {
459    /// Vector mask agnostic policy (bit `7`)
460    vma: bool,
461    /// Vector tail agnostic policy (bit `6`)
462    vta: bool,
463    /// Selected element width (bits `[5:3]`)
464    vsew: Vsew,
465    /// Vector length multiplier (bits `[2:0]`)
466    vlmul: Vlmul,
467}
468
469impl<const ELEN: u32, const VLEN: u32> Vtype<ELEN, VLEN> {
470    /// Vector mask agnostic policy (bit `7`)
471    pub const fn vma(&self) -> bool {
472        self.vma
473    }
474
475    /// Vector tail agnostic policy (bit `6`)
476    pub const fn vta(&self) -> bool {
477        self.vta
478    }
479
480    /// Selected element width (bits `[5:3]`)
481    pub const fn vsew(&self) -> Vsew {
482        self.vsew
483    }
484
485    /// Vector length multiplier (bits `[2:0]`)
486    pub const fn vlmul(&self) -> Vlmul {
487        self.vlmul
488    }
489
490    /// Decode from raw register value.
491    ///
492    /// The `XLEN` is taken from `Reg::XLEN` and must be 32 for RV32 or 64 for RV64. The `vill` bit
493    /// is placed at bit position `Reg::XLEN - 1`.
494    ///
495    /// All bits in `[Reg::XLEN-1:8]` must be zero; non-zero bits indicate an unrecognized
496    /// encoding and cause `None` to be returned (this includes `vill`).
497    #[inline(always)]
498    pub const fn from_raw<Reg>(raw: Reg::Type) -> Option<Self>
499    where
500        Reg: [const] Register,
501    {
502        let raw = raw.as_u64();
503
504        // All bits in [XLEN-1:8] must be zero
505        if (raw >> 8u8) != 0 {
506            cold_path();
507            return None;
508        }
509
510        let vlmul_bits = (raw & 0b111) as u8;
511        let vsew_bits = ((raw >> 3u8) & 0b111) as u8;
512        let vta = ((raw >> 6u8) & 1) != 0;
513        let vma = ((raw >> 7u8) & 1) != 0;
514
515        let Some(vlmul) = Vlmul::from_bits(vlmul_bits) else {
516            cold_path();
517            return None;
518        };
519        let Some(vsew) = Vsew::from_bits(vsew_bits) else {
520            cold_path();
521            return None;
522        };
523
524        let sew = vsew.bits_width();
525        if u32::from(sew) > ELEN {
526            cold_path();
527            return None;
528        }
529
530        if vlmul.vlmax::<VLEN>(vsew) == 0 {
531            cold_path();
532            return None;
533        }
534
535        Some(Self {
536            vma,
537            vta,
538            vsew,
539            vlmul,
540        })
541    }
542
543    /// Encode to a raw `vtype` register value of type `Reg::Type`.
544    ///
545    /// The encoded value contains `vlmul`, `vsew`, `vta`, and `vma` in bits `[7:0]` with
546    /// `vill = 0`. To construct a raw value with `vill = 1` (illegal configuration), use
547    /// [`Self::illegal_raw`].
548    #[inline(always)]
549    pub const fn to_raw<Reg>(self) -> Reg::Type
550    where
551        Reg: [const] Register,
552    {
553        let mut raw = 0u8;
554        raw |= self.vlmul.to_bits();
555        raw |= self.vsew.to_bits() << 3u8;
556
557        if self.vta {
558            raw |= 1 << 6u8;
559        }
560
561        if self.vma {
562            raw |= 1 << 7u8;
563        }
564
565        Reg::Type::from(raw)
566    }
567
568    /// Construct a raw value for `vtype` with `vill=1` (illegal configuration).
569    ///
570    /// Per spec: when `vill` is set, the remaining bits are zero and `vl` is also set to zero. Any
571    /// subsequent vector instruction that depends on `vtype` will raise an illegal-instruction
572    /// exception.
573    #[inline(always)]
574    pub const fn illegal_raw<Reg>() -> Reg::Type
575    where
576        Reg: [const] Register,
577    {
578        let vill_bit = Reg::XLEN - 1;
579        Reg::Type::from(1u8) << vill_bit
580    }
581}