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