Skip to main content

ab_riscv_primitives/instructions/rv32/zce/
zcmp.rs

1//! RV32 Zcmp extension
2
3#[cfg(test)]
4mod tests;
5
6use crate::hart::HartConfig;
7use crate::instructions::rv32::c::zca::Rv32ZcaInstruction;
8use crate::instructions::utils::{I24, Shamt};
9use crate::instructions::{ImplementedExtension, Instruction, IsaExtension};
10use crate::registers::general_purpose::{EReg, Reg, Register};
11use ab_riscv_macros::instruction;
12use core::fmt;
13use core::hint::unreachable_unchecked;
14use core::iter::TrustedLen;
15use core::marker::{Destruct, PhantomData};
16
17/// General purpose register with additional constraints for Zcmp extension
18///
19/// # Safety
20/// [`Register::from_bits()`] must return `Some()` for:
21/// * `1`, `8`, `9` and `18..=27` if [`Register::RVE`] is `false`
22/// * `1`, `8` and `9` if [`Register::RVE`] is `true`
23pub const unsafe trait ZcmpRegister
24where
25    Self: [const] Register,
26{
27}
28
29// SAFETY: [`Reg::from_bits()`] returns Some for all valid register numbers
30const unsafe impl ZcmpRegister for Reg<u32> {}
31
32// SAFETY: [`Reg::from_bits()`] returns Some for all valid register numbers
33const unsafe impl ZcmpRegister for Reg<u64> {}
34
35// SAFETY: [`EReg::from_bits()`] returns Some for all valid register numbers
36const unsafe impl ZcmpRegister for EReg<u32> {}
37
38// SAFETY: [`EReg::from_bits()`] returns Some for all valid register numbers
39const unsafe impl ZcmpRegister for EReg<u64> {}
40
41/// Values 0..=3 are reserved by the spec; only 4..=15 are valid.
42/// Construct via [`ZcmpUrlist::try_from_raw`].
43#[derive(Debug, Clone, Copy)]
44#[derive_const(PartialEq, Eq)]
45#[repr(u8)]
46enum ZcmpUrlistInner {
47    /// {ra}
48    Ra = 4,
49    /// {ra, s0}
50    RaS0 = 5,
51    /// {ra, s0-s1}
52    RaS0S1 = 6,
53    /// {ra, s0-s2}
54    RaS0S2 = 7,
55    /// {ra, s0-s3}
56    RaS0S3 = 8,
57    /// {ra, s0-s4}
58    RaS0S4 = 9,
59    /// {ra, s0-s5}
60    RaS0S5 = 10,
61    /// {ra, s0-s6}
62    RaS0S6 = 11,
63    /// {ra, s0-s7}
64    RaS0S7 = 12,
65    /// {ra, s0-s8}
66    RaS0S8 = 13,
67    /// {ra, s0-s9}
68    RaS0S9 = 14,
69    /// {ra, s0-s11}
70    ///
71    /// Note: s10 is skipped; urlist=15 maps directly to s0-s11 per spec.
72    RaS0S11 = 15,
73}
74
75/// Zcmp register list selector.
76///
77/// Only valid values (4..=15, further restricted to 4..=6 for RVE) are
78/// representable; construct via [`ZcmpUrlist::try_from_raw`].
79#[derive(Debug, Clone, Copy)]
80pub struct ZcmpUrlist<Reg> {
81    inner: ZcmpUrlistInner,
82    reg: PhantomData<Reg>,
83}
84
85const impl<Reg> PartialEq<ZcmpUrlist<Reg>> for ZcmpUrlist<Reg> {
86    #[inline(always)]
87    fn eq(&self, other: &ZcmpUrlist<Reg>) -> bool {
88        self.inner == other.inner
89    }
90}
91
92const impl<Reg> Eq for ZcmpUrlist<Reg> {}
93
94/// Iterator over the registers of a [`ZcmpUrlist`], see [`ZcmpUrlist::reg_list()`]
95#[derive(Debug, Clone)]
96struct ZcmpRegList<Reg> {
97    /// Absolute register numbers that are left to yield
98    bits: &'static [u8],
99    reg: PhantomData<Reg>,
100}
101
102const impl<Reg> Iterator for ZcmpRegList<Reg>
103where
104    Reg: [const] ZcmpRegister,
105{
106    type Item = Reg;
107
108    #[inline(always)]
109    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
110    fn next(&mut self) -> Option<Self::Item> {
111        let (&bits, rest) = self.bits.split_first()?;
112        self.bits = rest;
113
114        // SAFETY: `ZcmpRegister` requires `Reg::from_bits()` to return `Some` for every register
115        // number a `ZcmpUrlist` can contain
116        Some(unsafe { Reg::from_bits(bits).unwrap_unchecked() })
117    }
118
119    #[inline(always)]
120    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
121    fn size_hint(&self) -> (usize, Option<usize>) {
122        (self.bits.len(), Some(self.bits.len()))
123    }
124}
125
126impl<Reg> ExactSizeIterator for ZcmpRegList<Reg>
127where
128    Reg: ZcmpRegister,
129{
130    #[inline(always)]
131    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
132    fn len(&self) -> usize {
133        self.bits.len()
134    }
135}
136
137// SAFETY: `size_hint()` returns the exact number of registers left to yield
138const unsafe impl<Reg> TrustedLen for ZcmpRegList<Reg> where Reg: [const] ZcmpRegister {}
139
140impl<Reg> ZcmpUrlist<Reg>
141where
142    Reg: ZcmpRegister,
143{
144    const XLEN_32: u8 = 32;
145    const XLEN_64: u8 = 64;
146
147    /// Create a validated [`ZcmpUrlist`] from a raw `u8` value.
148    ///
149    /// Returns `None` if `raw` is reserved (0..=3), out of range (>15), or
150    /// names a register list inaccessible under the current ISA variant
151    /// (e.g., urlist > 6 under RVE, where only ra, s0, s1 exist).
152    #[inline(always)]
153    pub const fn try_from_raw(raw: u8) -> Option<Self>
154    where
155        Reg: [const] ZcmpRegister,
156    {
157        if !(Reg::XLEN == Self::XLEN_32 || Reg::XLEN == Self::XLEN_64) {
158            return None;
159        }
160
161        let inner = if Reg::RVE {
162            // RVE only has access to ra(x1), s0(x8), s1(x9)
163            match raw {
164                4 => ZcmpUrlistInner::Ra,
165                5 => ZcmpUrlistInner::RaS0,
166                6 => ZcmpUrlistInner::RaS0S1,
167                _ => {
168                    return None;
169                }
170            }
171        } else {
172            match raw {
173                4 => ZcmpUrlistInner::Ra,
174                5 => ZcmpUrlistInner::RaS0,
175                6 => ZcmpUrlistInner::RaS0S1,
176                7 => ZcmpUrlistInner::RaS0S2,
177                8 => ZcmpUrlistInner::RaS0S3,
178                9 => ZcmpUrlistInner::RaS0S4,
179                10 => ZcmpUrlistInner::RaS0S5,
180                11 => ZcmpUrlistInner::RaS0S6,
181                12 => ZcmpUrlistInner::RaS0S7,
182                13 => ZcmpUrlistInner::RaS0S8,
183                14 => ZcmpUrlistInner::RaS0S9,
184                15 => ZcmpUrlistInner::RaS0S11,
185                _ => {
186                    return None;
187                }
188            }
189        };
190
191        Some(Self {
192            inner,
193            reg: PhantomData,
194        })
195    }
196
197    /// Convert to the raw `u8` discriminant (4..=15).
198    #[inline(always)]
199    pub const fn as_u8(self) -> u8 {
200        self.inner as u8
201    }
202
203    /// Iterator over the registers in this list.
204    ///
205    /// Order matches the spec push/pop order: ra first, then s0 ascending.
206    /// ra=x1, s0=x8, s1=x9, s2=x18..s9=x25, s10=x26, s11=x27.
207    ///
208    /// Note: urlist=15 is {ra, s0-s11} (13 registers, including s10);
209    /// {ra, s0-s10} has no encoding.
210    #[inline]
211    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
212    pub const fn reg_list(
213        self,
214    ) -> impl [const] Iterator<Item = Reg> + ExactSizeIterator + [const] TrustedLen + const Destruct
215    where
216        Reg: [const] ZcmpRegister,
217    {
218        let bits: &[u8] = match self.inner {
219            ZcmpUrlistInner::Ra => &[1],
220            ZcmpUrlistInner::RaS0 => &[1, 8],
221            ZcmpUrlistInner::RaS0S1 => &[1, 8, 9],
222            ZcmpUrlistInner::RaS0S2 => &[1, 8, 9, 18],
223            ZcmpUrlistInner::RaS0S3 => &[1, 8, 9, 18, 19],
224            ZcmpUrlistInner::RaS0S4 => &[1, 8, 9, 18, 19, 20],
225            ZcmpUrlistInner::RaS0S5 => &[1, 8, 9, 18, 19, 20, 21],
226            ZcmpUrlistInner::RaS0S6 => &[1, 8, 9, 18, 19, 20, 21, 22],
227            ZcmpUrlistInner::RaS0S7 => &[1, 8, 9, 18, 19, 20, 21, 22, 23],
228            ZcmpUrlistInner::RaS0S8 => &[1, 8, 9, 18, 19, 20, 21, 22, 23, 24],
229            ZcmpUrlistInner::RaS0S9 => &[1, 8, 9, 18, 19, 20, 21, 22, 23, 24, 25],
230            ZcmpUrlistInner::RaS0S11 => &[1, 8, 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],
231        };
232
233        ZcmpRegList {
234            bits,
235            reg: PhantomData,
236        }
237    }
238
239    /// Stack adjustment base in bytes.
240    ///
241    /// The minimum stack frame size for this register list, rounded up to a 16-byte alignment. The
242    /// full stack adjustment is: `stack_adj_base + spimm * 16`.
243    ///
244    /// Values sourced from the Zcmp spec Table 3.
245    #[inline(always)]
246    pub const fn stack_adj_base(self) -> u8 {
247        match Reg::XLEN {
248            // RV32: each register is 4 bytes; base = ceil(n_regs * 4 / 16) * 16
249            Self::XLEN_32 => match self.inner {
250                ZcmpUrlistInner::Ra
251                | ZcmpUrlistInner::RaS0
252                | ZcmpUrlistInner::RaS0S1
253                | ZcmpUrlistInner::RaS0S2 => 16,
254                ZcmpUrlistInner::RaS0S3
255                | ZcmpUrlistInner::RaS0S4
256                | ZcmpUrlistInner::RaS0S5
257                | ZcmpUrlistInner::RaS0S6 => 32,
258                ZcmpUrlistInner::RaS0S7 | ZcmpUrlistInner::RaS0S8 | ZcmpUrlistInner::RaS0S9 => 48,
259                ZcmpUrlistInner::RaS0S11 => 64,
260            },
261            // RV64: each register is 8 bytes; base = ceil(n_regs * 8 / 16) * 16
262            Self::XLEN_64 => match self.inner {
263                ZcmpUrlistInner::Ra | ZcmpUrlistInner::RaS0 => 16,
264                ZcmpUrlistInner::RaS0S1 | ZcmpUrlistInner::RaS0S2 => 32,
265                ZcmpUrlistInner::RaS0S3 | ZcmpUrlistInner::RaS0S4 => 48,
266                ZcmpUrlistInner::RaS0S5 | ZcmpUrlistInner::RaS0S6 => 64,
267                ZcmpUrlistInner::RaS0S7 | ZcmpUrlistInner::RaS0S8 => 80,
268                ZcmpUrlistInner::RaS0S9 => 96,
269                ZcmpUrlistInner::RaS0S11 => 112,
270            },
271            _ => {
272                // SAFETY: Invariant protected by constructor guarantees that `Reg::XLEN` is one of
273                // the two above values
274                unsafe { unreachable_unchecked() }
275            }
276        }
277    }
278}
279
280impl<Reg> fmt::Display for ZcmpUrlist<Reg> {
281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282        match self.inner {
283            ZcmpUrlistInner::Ra => write!(f, "{{ra}}"),
284            ZcmpUrlistInner::RaS0 => write!(f, "{{ra, s0}}"),
285            ZcmpUrlistInner::RaS0S1 => write!(f, "{{ra, s0-s1}}"),
286            ZcmpUrlistInner::RaS0S2 => write!(f, "{{ra, s0-s2}}"),
287            ZcmpUrlistInner::RaS0S3 => write!(f, "{{ra, s0-s3}}"),
288            ZcmpUrlistInner::RaS0S4 => write!(f, "{{ra, s0-s4}}"),
289            ZcmpUrlistInner::RaS0S5 => write!(f, "{{ra, s0-s5}}"),
290            ZcmpUrlistInner::RaS0S6 => write!(f, "{{ra, s0-s6}}"),
291            ZcmpUrlistInner::RaS0S7 => write!(f, "{{ra, s0-s7}}"),
292            ZcmpUrlistInner::RaS0S8 => write!(f, "{{ra, s0-s8}}"),
293            ZcmpUrlistInner::RaS0S9 => write!(f, "{{ra, s0-s9}}"),
294            ZcmpUrlistInner::RaS0S11 => write!(f, "{{ra, s0-s11}}"),
295        }
296    }
297}
298
299/// Zcmp compressed instruction set
300#[instruction(
301    inherit = [Rv32ZcaInstruction, Rv32ZcmpOnlyInstruction],
302)]
303#[derive(Debug, Clone, Copy)]
304#[derive_const(PartialEq, Eq)]
305pub enum Rv32ZcmpInstruction<Hart>
306where
307    Hart: HartConfig, {}
308
309#[instruction]
310const impl<Reg, Hart> Instruction for Rv32ZcmpInstruction<Hart>
311where
312    Reg: [const] Register<Type = u32>,
313    Hart: [const] HartConfig<Reg = Reg>,
314{
315    const OWN_ISA_EXTENSIONS: &'static [IsaExtension] = &[IsaExtension::new("zcmp", 1, 0)];
316
317    const ALIGNMENT: u8 = align_of::<u16>() as u8;
318
319    type Hart = Hart;
320
321    #[inline(always)]
322    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
323    fn try_decode(instruction: u32) -> Option<Self> {
324        None
325    }
326
327    #[inline(always)]
328    fn size(&self) -> u8 {
329        size_of::<u16>() as u8
330    }
331}
332
333#[instruction]
334impl<Reg, Hart> fmt::Display for Rv32ZcmpInstruction<Hart>
335where
336    Reg: Register,
337    Hart: HartConfig<Reg = Reg>,
338{
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        match self {}
341    }
342}
343
344/// Instruction that contains isolated Zcmp instructions without inheriting Zca for testing purposes
345#[instruction]
346#[derive(Debug, Clone, Copy)]
347#[derive_const(PartialEq, Eq)]
348#[doc(hidden)]
349pub enum Rv32ZcmpOnlyInstruction<Hart>
350where
351    Hart: HartConfig,
352{
353    /// CM.PUSH - push reg_list, decrement sp by `stack_adj`
354    ///
355    /// `stack_adj = urlist.stack_adj_base() + spimm * 16` from the encoding.
356    CmPush {
357        urlist: ZcmpUrlist<Hart::Reg>,
358        stack_adj: u8,
359    },
360    /// CM.POP - pop reg_list, increment sp by `stack_adj` (no return)
361    CmPop {
362        urlist: ZcmpUrlist<Hart::Reg>,
363        stack_adj: u8,
364    },
365    /// CM.POPRETZ - pop reg_list, set a0=0, increment sp, return
366    CmPopretz {
367        urlist: ZcmpUrlist<Hart::Reg>,
368        stack_adj: u8,
369    },
370    /// CM.POPRET - pop reg_list, increment sp, return
371    CmPopret {
372        urlist: ZcmpUrlist<Hart::Reg>,
373        stack_adj: u8,
374    },
375    /// CM.MVA01S - a0 = r1s', a1 = r2s'.
376    ///
377    /// The fields are called both r1s/r2s and rs1/rs2 in the spec, rs1/rs2 is used here for
378    /// consistency with other instructions.
379    CmMva01s { rs1: Hart::Reg, rs2: Hart::Reg },
380    /// CM.MVSA01 - r1s' = a0, r2s' = a1  (r1s' != r2s').
381    ///
382    /// The fields are called both r1s/r2s and rs1/rs2 in the spec, rs1/rs2 is used here for
383    /// consistency with other instructions.
384    CmMvsa01 { rs1: Hart::Reg, rs2: Hart::Reg },
385}
386
387#[instruction]
388const impl<Reg, Hart> Instruction for Rv32ZcmpOnlyInstruction<Hart>
389where
390    Reg: [const] ZcmpRegister<Type = u32>,
391    Hart: [const] HartConfig<Reg = Reg>,
392{
393    const OWN_ISA_EXTENSIONS: &'static [IsaExtension] = &[];
394
395    const ALIGNMENT: u8 = align_of::<u16>() as u8;
396
397    type Hart = Hart;
398
399    #[inline(always)]
400    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
401    fn try_decode(instruction: u32) -> Option<Self> {
402        /// Map the Zcmp 3-bit "s-register" field to an absolute register number.
403        /// 000->x8(s0), 001->x9(s1), 010->x18(s2)..111->x23(s7)
404        #[inline(always)]
405        const fn sreg_bits(field: u8) -> u8 {
406            match field {
407                0 => 8,
408                1 => 9,
409                f => f + 16,
410            }
411        }
412
413        let inst = instruction as u16;
414        let quadrant = inst & 0b11;
415        let funct3 = ((inst >> 13) & 0b111) as u8;
416
417        // All Zcmp instructions: Q10, funct3=101
418        if quadrant != 0b10 || funct3 != 0b101 {
419            None?;
420        }
421
422        let funct2_12_11 = ((inst >> 11) & 0b11) as u8;
423
424        match funct2_12_11 {
425            // CM.PUSH / CM.POP / CM.POPRETZ / CM.POPRET
426            0b11 => {
427                let op_sel = ((inst >> 9) & 0b11) as u8;
428                let urlist = ZcmpUrlist::try_from_raw(((inst >> 4) & 0xf) as u8)?;
429                let spimm = ((inst >> 2) & 0b11) as u8;
430                let stack_adj = urlist.stack_adj_base() + spimm * 16;
431                match op_sel {
432                    0b00 => Some(Self::CmPush { urlist, stack_adj }),
433                    0b01 => Some(Self::CmPop { urlist, stack_adj }),
434                    0b10 => Some(Self::CmPopretz { urlist, stack_adj }),
435                    0b11 => Some(Self::CmPopret { urlist, stack_adj }),
436                    _ => None,
437                }
438            }
439            // CM.MVA01S / CM.MVSA01: require bit 10 = 1 (full funct6 = 101_011)
440            0b01 => {
441                if (inst >> 10) & 1 != 1 {
442                    None?;
443                }
444
445                let r1s_bits = ((inst >> 7) & 0b111) as u8;
446                let funct2 = ((inst >> 5) & 0b11) as u8;
447                let r2s_bits = ((inst >> 2) & 0b111) as u8;
448
449                // Reg::from_bits returns None for registers inaccessible in the current ISA
450                // variant. Under RVE this covers field > 1 (i.e. r1sc/r2sc > 1 in the spec
451                // pseudocode), which maps to x18-x23 - registers that do not exist in the E
452                // extension.
453                let r1s = Reg::from_bits(sreg_bits(r1s_bits))?;
454                let r2s = Reg::from_bits(sreg_bits(r2s_bits))?;
455
456                // funct2[6:5]: 0b11 -> CM.MVA01S, 0b01 -> CM.MVSA01, others reserved
457                match funct2 {
458                    0b11 => Some(Self::CmMva01s { rs1: r1s, rs2: r2s }),
459                    0b01 => {
460                        // CM.MVSA01 requires r1s' != r2s'
461                        if r1s_bits == r2s_bits {
462                            None?;
463                        }
464                        Some(Self::CmMvsa01 { rs1: r1s, rs2: r2s })
465                    }
466                    _ => None,
467                }
468            }
469            // funct2_12_11 values 0b00 and 0b10 are not defined by Zcmp
470            _ => None,
471        }
472    }
473
474    #[inline(always)]
475    fn size(&self) -> u8 {
476        size_of::<u16>() as u8
477    }
478}
479
480#[instruction]
481impl<Reg, Hart> fmt::Display for Rv32ZcmpOnlyInstruction<Hart>
482where
483    Reg: Register,
484    Hart: HartConfig<Reg = Reg>,
485{
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        match self {
488            Self::CmPush { urlist, stack_adj } => {
489                write!(f, "cm.push {urlist}, -{stack_adj}")
490            }
491            Self::CmPop { urlist, stack_adj } => {
492                write!(f, "cm.pop {urlist}, {stack_adj}")
493            }
494            Self::CmPopretz { urlist, stack_adj } => {
495                write!(f, "cm.popretz {urlist}, {stack_adj}")
496            }
497            Self::CmPopret { urlist, stack_adj } => {
498                write!(f, "cm.popret {urlist}, {stack_adj}")
499            }
500            Self::CmMva01s { rs1, rs2 } => write!(f, "cm.mva01s {rs1}, {rs2}"),
501            Self::CmMvsa01 { rs1, rs2 } => write!(f, "cm.mvsa01 {rs1}, {rs2}"),
502        }
503    }
504}