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}