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            // TODO: Associated constant doesn't compile right now:
250            //  https://github.com/rust-lang/rust/issues/162679
251            // Self::XLEN_32
252            32 => match self.inner {
253                ZcmpUrlistInner::Ra
254                | ZcmpUrlistInner::RaS0
255                | ZcmpUrlistInner::RaS0S1
256                | ZcmpUrlistInner::RaS0S2 => 16,
257                ZcmpUrlistInner::RaS0S3
258                | ZcmpUrlistInner::RaS0S4
259                | ZcmpUrlistInner::RaS0S5
260                | ZcmpUrlistInner::RaS0S6 => 32,
261                ZcmpUrlistInner::RaS0S7 | ZcmpUrlistInner::RaS0S8 | ZcmpUrlistInner::RaS0S9 => 48,
262                ZcmpUrlistInner::RaS0S11 => 64,
263            },
264            // RV64: each register is 8 bytes; base = ceil(n_regs * 8 / 16) * 16
265            // TODO: Associated constant doesn't compile right now:
266            //  https://github.com/rust-lang/rust/issues/162679
267            // Self::XLEN_64
268            64 => match self.inner {
269                ZcmpUrlistInner::Ra | ZcmpUrlistInner::RaS0 => 16,
270                ZcmpUrlistInner::RaS0S1 | ZcmpUrlistInner::RaS0S2 => 32,
271                ZcmpUrlistInner::RaS0S3 | ZcmpUrlistInner::RaS0S4 => 48,
272                ZcmpUrlistInner::RaS0S5 | ZcmpUrlistInner::RaS0S6 => 64,
273                ZcmpUrlistInner::RaS0S7 | ZcmpUrlistInner::RaS0S8 => 80,
274                ZcmpUrlistInner::RaS0S9 => 96,
275                ZcmpUrlistInner::RaS0S11 => 112,
276            },
277            _ => {
278                // SAFETY: Invariant protected by constructor guarantees that `Reg::XLEN` is one of
279                // the two above values
280                unsafe { unreachable_unchecked() }
281            }
282        }
283    }
284}
285
286impl<Reg> fmt::Display for ZcmpUrlist<Reg> {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        match self.inner {
289            ZcmpUrlistInner::Ra => write!(f, "{{ra}}"),
290            ZcmpUrlistInner::RaS0 => write!(f, "{{ra, s0}}"),
291            ZcmpUrlistInner::RaS0S1 => write!(f, "{{ra, s0-s1}}"),
292            ZcmpUrlistInner::RaS0S2 => write!(f, "{{ra, s0-s2}}"),
293            ZcmpUrlistInner::RaS0S3 => write!(f, "{{ra, s0-s3}}"),
294            ZcmpUrlistInner::RaS0S4 => write!(f, "{{ra, s0-s4}}"),
295            ZcmpUrlistInner::RaS0S5 => write!(f, "{{ra, s0-s5}}"),
296            ZcmpUrlistInner::RaS0S6 => write!(f, "{{ra, s0-s6}}"),
297            ZcmpUrlistInner::RaS0S7 => write!(f, "{{ra, s0-s7}}"),
298            ZcmpUrlistInner::RaS0S8 => write!(f, "{{ra, s0-s8}}"),
299            ZcmpUrlistInner::RaS0S9 => write!(f, "{{ra, s0-s9}}"),
300            ZcmpUrlistInner::RaS0S11 => write!(f, "{{ra, s0-s11}}"),
301        }
302    }
303}
304
305/// Zcmp compressed instruction set
306#[instruction(
307    inherit = [Rv32ZcaInstruction, Rv32ZcmpOnlyInstruction],
308)]
309#[derive(Debug, Clone, Copy)]
310#[derive_const(PartialEq, Eq)]
311pub enum Rv32ZcmpInstruction<Hart>
312where
313    Hart: HartConfig, {}
314
315#[instruction]
316const impl<Reg, Hart> Instruction for Rv32ZcmpInstruction<Hart>
317where
318    Reg: [const] Register<Type = u32>,
319    Hart: [const] HartConfig<Reg = Reg>,
320{
321    const OWN_ISA_EXTENSIONS: &'static [IsaExtension] = &[IsaExtension::new("zcmp", 1, 0)];
322
323    const ALIGNMENT: u8 = align_of::<u16>() as u8;
324
325    type Hart = Hart;
326
327    #[inline(always)]
328    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
329    fn try_decode(instruction: u32) -> Option<Self> {
330        None
331    }
332
333    #[inline(always)]
334    fn size(&self) -> u8 {
335        size_of::<u16>() as u8
336    }
337}
338
339#[instruction]
340impl<Reg, Hart> fmt::Display for Rv32ZcmpInstruction<Hart>
341where
342    Reg: Register,
343    Hart: HartConfig<Reg = Reg>,
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<Hart>
356where
357    Hart: HartConfig,
358{
359    /// CM.PUSH - push reg_list, decrement sp by `stack_adj`
360    ///
361    /// `stack_adj = urlist.stack_adj_base() + spimm * 16` from the encoding.
362    CmPush {
363        urlist: ZcmpUrlist<Hart::Reg>,
364        stack_adj: u8,
365    },
366    /// CM.POP - pop reg_list, increment sp by `stack_adj` (no return)
367    CmPop {
368        urlist: ZcmpUrlist<Hart::Reg>,
369        stack_adj: u8,
370    },
371    /// CM.POPRETZ - pop reg_list, set a0=0, increment sp, return
372    CmPopretz {
373        urlist: ZcmpUrlist<Hart::Reg>,
374        stack_adj: u8,
375    },
376    /// CM.POPRET - pop reg_list, increment sp, return
377    CmPopret {
378        urlist: ZcmpUrlist<Hart::Reg>,
379        stack_adj: u8,
380    },
381    /// CM.MVA01S - a0 = r1s', a1 = r2s'.
382    ///
383    /// The fields are called both r1s/r2s and rs1/rs2 in the spec, rs1/rs2 is used here for
384    /// consistency with other instructions.
385    CmMva01s { rs1: Hart::Reg, rs2: Hart::Reg },
386    /// CM.MVSA01 - r1s' = a0, r2s' = a1  (r1s' != r2s').
387    ///
388    /// The fields are called both r1s/r2s and rs1/rs2 in the spec, rs1/rs2 is used here for
389    /// consistency with other instructions.
390    CmMvsa01 { rs1: Hart::Reg, rs2: Hart::Reg },
391}
392
393#[instruction]
394const impl<Reg, Hart> Instruction for Rv32ZcmpOnlyInstruction<Hart>
395where
396    Reg: [const] ZcmpRegister<Type = u32>,
397    Hart: [const] HartConfig<Reg = Reg>,
398{
399    const OWN_ISA_EXTENSIONS: &'static [IsaExtension] = &[];
400
401    const ALIGNMENT: u8 = align_of::<u16>() as u8;
402
403    type Hart = Hart;
404
405    #[inline(always)]
406    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
407    fn try_decode(instruction: u32) -> Option<Self> {
408        /// Map the Zcmp 3-bit "s-register" field to an absolute register number.
409        /// 000->x8(s0), 001->x9(s1), 010->x18(s2)..111->x23(s7)
410        #[inline(always)]
411        const fn sreg_bits(field: u8) -> u8 {
412            match field {
413                0 => 8,
414                1 => 9,
415                f => f + 16,
416            }
417        }
418
419        let inst = instruction as u16;
420        let quadrant = inst & 0b11;
421        let funct3 = ((inst >> 13) & 0b111) as u8;
422
423        // All Zcmp instructions: Q10, funct3=101
424        if quadrant != 0b10 || funct3 != 0b101 {
425            None?;
426        }
427
428        let funct2_12_11 = ((inst >> 11) & 0b11) as u8;
429
430        match funct2_12_11 {
431            // CM.PUSH / CM.POP / CM.POPRETZ / CM.POPRET
432            0b11 => {
433                let op_sel = ((inst >> 9) & 0b11) as u8;
434                let urlist = ZcmpUrlist::try_from_raw(((inst >> 4) & 0xf) as u8)?;
435                let spimm = ((inst >> 2) & 0b11) as u8;
436                let stack_adj = urlist.stack_adj_base() + spimm * 16;
437                match op_sel {
438                    0b00 => Some(Self::CmPush { urlist, stack_adj }),
439                    0b01 => Some(Self::CmPop { urlist, stack_adj }),
440                    0b10 => Some(Self::CmPopretz { urlist, stack_adj }),
441                    0b11 => Some(Self::CmPopret { urlist, stack_adj }),
442                    _ => None,
443                }
444            }
445            // CM.MVA01S / CM.MVSA01: require bit 10 = 1 (full funct6 = 101_011)
446            0b01 => {
447                if (inst >> 10) & 1 != 1 {
448                    None?;
449                }
450
451                let r1s_bits = ((inst >> 7) & 0b111) as u8;
452                let funct2 = ((inst >> 5) & 0b11) as u8;
453                let r2s_bits = ((inst >> 2) & 0b111) as u8;
454
455                // Reg::from_bits returns None for registers inaccessible in the current ISA
456                // variant. Under RVE this covers field > 1 (i.e. r1sc/r2sc > 1 in the spec
457                // pseudocode), which maps to x18-x23 - registers that do not exist in the E
458                // extension.
459                let r1s = Reg::from_bits(sreg_bits(r1s_bits))?;
460                let r2s = Reg::from_bits(sreg_bits(r2s_bits))?;
461
462                // funct2[6:5]: 0b11 -> CM.MVA01S, 0b01 -> CM.MVSA01, others reserved
463                match funct2 {
464                    0b11 => Some(Self::CmMva01s { rs1: r1s, rs2: r2s }),
465                    0b01 => {
466                        // CM.MVSA01 requires r1s' != r2s'
467                        if r1s_bits == r2s_bits {
468                            None?;
469                        }
470                        Some(Self::CmMvsa01 { rs1: r1s, rs2: r2s })
471                    }
472                    _ => None,
473                }
474            }
475            // funct2_12_11 values 0b00 and 0b10 are not defined by Zcmp
476            _ => None,
477        }
478    }
479
480    #[inline(always)]
481    fn size(&self) -> u8 {
482        size_of::<u16>() as u8
483    }
484}
485
486#[instruction]
487impl<Reg, Hart> fmt::Display for Rv32ZcmpOnlyInstruction<Hart>
488where
489    Reg: Register,
490    Hart: HartConfig<Reg = Reg>,
491{
492    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493        match self {
494            Self::CmPush { urlist, stack_adj } => {
495                write!(f, "cm.push {urlist}, -{stack_adj}")
496            }
497            Self::CmPop { urlist, stack_adj } => {
498                write!(f, "cm.pop {urlist}, {stack_adj}")
499            }
500            Self::CmPopretz { urlist, stack_adj } => {
501                write!(f, "cm.popretz {urlist}, {stack_adj}")
502            }
503            Self::CmPopret { urlist, stack_adj } => {
504                write!(f, "cm.popret {urlist}, {stack_adj}")
505            }
506            Self::CmMva01s { rs1, rs2 } => write!(f, "cm.mva01s {rs1}, {rs2}"),
507            Self::CmMvsa01 { rs1, rs2 } => write!(f, "cm.mvsa01 {rs1}, {rs2}"),
508        }
509    }
510}