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