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
30unsafe impl const ZcmpRegister for Reg<u32> {
31    const RVE: bool = false;
32}
33
34// SAFETY: [`Reg::from_bits()`] returns Some for all valid register numbers
35unsafe impl const ZcmpRegister for Reg<u64> {
36    const RVE: bool = false;
37}
38
39// SAFETY: [`EReg::from_bits()`] returns Some for all valid register numbers
40unsafe impl const ZcmpRegister for EReg<u32> {
41    const RVE: bool = true;
42}
43
44// SAFETY: [`EReg::from_bits()`] returns Some for all valid register numbers
45unsafe impl const 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    pub fn reg_list(self) -> impl Iterator<Item = Reg> {
164        let regs: &[u8] = match self.inner {
165            ZcmpUrlistInner::Ra => &[1],
166            ZcmpUrlistInner::RaS0 => &[1, 8],
167            ZcmpUrlistInner::RaS0S1 => &[1, 8, 9],
168            ZcmpUrlistInner::RaS0S2 => &[1, 8, 9, 18],
169            ZcmpUrlistInner::RaS0S3 => &[1, 8, 9, 18, 19],
170            ZcmpUrlistInner::RaS0S4 => &[1, 8, 9, 18, 19, 20],
171            ZcmpUrlistInner::RaS0S5 => &[1, 8, 9, 18, 19, 20, 21],
172            ZcmpUrlistInner::RaS0S6 => &[1, 8, 9, 18, 19, 20, 21, 22],
173            ZcmpUrlistInner::RaS0S7 => &[1, 8, 9, 18, 19, 20, 21, 22, 23],
174            ZcmpUrlistInner::RaS0S8 => &[1, 8, 9, 18, 19, 20, 21, 22, 23, 24],
175            ZcmpUrlistInner::RaS0S9 => &[1, 8, 9, 18, 19, 20, 21, 22, 23, 24, 25],
176            ZcmpUrlistInner::RaS0S11 => &[1, 8, 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],
177        };
178
179        regs.iter().map(|&bits| {
180            // SAFETY: constructor invariant guarantees Reg::from_bits returns
181            // Some for all register numbers in these lists
182            unsafe { Reg::from_bits(bits).unwrap_unchecked() }
183        })
184    }
185
186    /// Stack adjustment base in bytes.
187    ///
188    /// The minimum stack frame size for this register list, rounded up to a 16-byte alignment. The
189    /// full stack adjustment is: `stack_adj_base + spimm * 16`.
190    ///
191    /// Values sourced from the Zcmp spec Table 3.
192    #[inline(always)]
193    pub const fn stack_adj_base(self) -> u8 {
194        match Reg::XLEN {
195            // RV32: each register is 4 bytes; base = ceil(n_regs * 4 / 16) * 16
196            Self::XLEN_32 => match self.inner {
197                ZcmpUrlistInner::Ra
198                | ZcmpUrlistInner::RaS0
199                | ZcmpUrlistInner::RaS0S1
200                | ZcmpUrlistInner::RaS0S2 => 16,
201                ZcmpUrlistInner::RaS0S3
202                | ZcmpUrlistInner::RaS0S4
203                | ZcmpUrlistInner::RaS0S5
204                | ZcmpUrlistInner::RaS0S6 => 32,
205                ZcmpUrlistInner::RaS0S7 | ZcmpUrlistInner::RaS0S8 | ZcmpUrlistInner::RaS0S9 => 48,
206                ZcmpUrlistInner::RaS0S11 => 64,
207            },
208            // RV64: each register is 8 bytes; base = ceil(n_regs * 8 / 16) * 16
209            Self::XLEN_64 => match self.inner {
210                ZcmpUrlistInner::Ra | ZcmpUrlistInner::RaS0 => 16,
211                ZcmpUrlistInner::RaS0S1 | ZcmpUrlistInner::RaS0S2 => 32,
212                ZcmpUrlistInner::RaS0S3 | ZcmpUrlistInner::RaS0S4 => 48,
213                ZcmpUrlistInner::RaS0S5 | ZcmpUrlistInner::RaS0S6 => 64,
214                ZcmpUrlistInner::RaS0S7 | ZcmpUrlistInner::RaS0S8 => 80,
215                ZcmpUrlistInner::RaS0S9 => 96,
216                ZcmpUrlistInner::RaS0S11 => 112,
217            },
218            _ => {
219                // SAFETY: Invariant protected by constructor guarantees that `Reg::XLEN` is one of
220                // the two above values
221                unsafe { unreachable_unchecked() }
222            }
223        }
224    }
225}
226
227impl<Reg> fmt::Display for ZcmpUrlist<Reg> {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        match self.inner {
230            ZcmpUrlistInner::Ra => write!(f, "{{ra}}"),
231            ZcmpUrlistInner::RaS0 => write!(f, "{{ra, s0}}"),
232            ZcmpUrlistInner::RaS0S1 => write!(f, "{{ra, s0-s1}}"),
233            ZcmpUrlistInner::RaS0S2 => write!(f, "{{ra, s0-s2}}"),
234            ZcmpUrlistInner::RaS0S3 => write!(f, "{{ra, s0-s3}}"),
235            ZcmpUrlistInner::RaS0S4 => write!(f, "{{ra, s0-s4}}"),
236            ZcmpUrlistInner::RaS0S5 => write!(f, "{{ra, s0-s5}}"),
237            ZcmpUrlistInner::RaS0S6 => write!(f, "{{ra, s0-s6}}"),
238            ZcmpUrlistInner::RaS0S7 => write!(f, "{{ra, s0-s7}}"),
239            ZcmpUrlistInner::RaS0S8 => write!(f, "{{ra, s0-s8}}"),
240            ZcmpUrlistInner::RaS0S9 => write!(f, "{{ra, s0-s9}}"),
241            ZcmpUrlistInner::RaS0S11 => write!(f, "{{ra, s0-s11}}"),
242        }
243    }
244}
245
246/// Zcmp compressed instruction set
247#[instruction(
248    inherit = [Rv32ZcaInstruction, Rv32ZcmpOnlyInstruction],
249)]
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum Rv32ZcmpInstruction<Reg> {}
252
253#[instruction]
254impl<Reg> const Instruction for Rv32ZcmpInstruction<Reg>
255where
256    Reg: [const] Register<Type = u32>,
257{
258    type Reg = Reg;
259
260    #[inline(always)]
261    fn try_decode(instruction: u32) -> Option<Self> {
262        None
263    }
264
265    #[inline(always)]
266    fn alignment() -> u8 {
267        align_of::<u16>() as u8
268    }
269
270    #[inline(always)]
271    fn size(&self) -> u8 {
272        size_of::<u16>() as u8
273    }
274}
275
276#[instruction]
277impl<Reg> fmt::Display for Rv32ZcmpInstruction<Reg>
278where
279    Reg: Register,
280{
281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282        match self {}
283    }
284}
285
286/// Instruction that contains isolated Zcmp instructions without inheriting Zca for testing purposes
287#[instruction]
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289#[doc(hidden)]
290pub enum Rv32ZcmpOnlyInstruction<Reg> {
291    /// CM.PUSH - push reg_list, decrement sp by `stack_adj`
292    ///
293    /// `stack_adj = urlist.stack_adj_base() + spimm * 16` from the encoding.
294    CmPush {
295        urlist: ZcmpUrlist<Reg>,
296        stack_adj: u8,
297    },
298    /// CM.POP - pop reg_list, increment sp by `stack_adj` (no return)
299    CmPop {
300        urlist: ZcmpUrlist<Reg>,
301        stack_adj: u8,
302    },
303    /// CM.POPRETZ - pop reg_list, set a0=0, increment sp, return
304    CmPopretz {
305        urlist: ZcmpUrlist<Reg>,
306        stack_adj: u8,
307    },
308    /// CM.POPRET - pop reg_list, increment sp, return
309    CmPopret {
310        urlist: ZcmpUrlist<Reg>,
311        stack_adj: u8,
312    },
313    /// CM.MVA01S - a0 = r1s', a1 = r2s'.
314    ///
315    /// The fields are called both r1s/r2s and rs1/rs2 in the spec, rs1/rs2 is used here for
316    /// consistency with other instructions.
317    CmMva01s { rs1: Reg, rs2: Reg },
318    /// CM.MVSA01 - r1s' = a0, r2s' = a1  (r1s' != r2s').
319    ///
320    /// The fields are called both r1s/r2s and rs1/rs2 in the spec, rs1/rs2 is used here for
321    /// consistency with other instructions.
322    CmMvsa01 { rs1: Reg, rs2: Reg },
323}
324
325#[instruction]
326impl<Reg> const Instruction for Rv32ZcmpOnlyInstruction<Reg>
327where
328    Reg: [const] ZcmpRegister<Type = u32>,
329{
330    type Reg = Reg;
331
332    #[inline(always)]
333    fn try_decode(instruction: u32) -> Option<Self> {
334        /// Map the Zcmp 3-bit "s-register" field to an absolute register number.
335        /// 000->x8(s0), 001->x9(s1), 010->x18(s2)..111->x23(s7)
336        #[inline(always)]
337        const fn sreg_bits(field: u8) -> u8 {
338            match field {
339                0 => 8,
340                1 => 9,
341                f => f + 16,
342            }
343        }
344
345        let inst = instruction as u16;
346        let quadrant = inst & 0b11;
347        let funct3 = ((inst >> 13) & 0b111) as u8;
348
349        // All Zcmp instructions: Q10, funct3=101
350        if quadrant != 0b10 || funct3 != 0b101 {
351            None?;
352        }
353
354        let funct2_12_11 = ((inst >> 11) & 0b11) as u8;
355
356        match funct2_12_11 {
357            // CM.PUSH / CM.POP / CM.POPRETZ / CM.POPRET
358            0b11 => {
359                let op_sel = ((inst >> 9) & 0b11) as u8;
360                let urlist = ZcmpUrlist::try_from_raw(((inst >> 4) & 0xf) as u8)?;
361                let spimm = ((inst >> 2) & 0b11) as u8;
362                let stack_adj = urlist.stack_adj_base() + spimm * 16;
363                match op_sel {
364                    0b00 => Some(Self::CmPush { urlist, stack_adj }),
365                    0b01 => Some(Self::CmPop { urlist, stack_adj }),
366                    0b10 => Some(Self::CmPopretz { urlist, stack_adj }),
367                    0b11 => Some(Self::CmPopret { urlist, stack_adj }),
368                    _ => None,
369                }
370            }
371            // CM.MVA01S / CM.MVSA01: require bit 10 = 1 (full funct6 = 101_011)
372            0b01 => {
373                if (inst >> 10) & 1 != 1 {
374                    None?;
375                }
376
377                let r1s_bits = ((inst >> 7) & 0b111) as u8;
378                let funct2 = ((inst >> 5) & 0b11) as u8;
379                let r2s_bits = ((inst >> 2) & 0b111) as u8;
380
381                // Reg::from_bits returns None for registers inaccessible in the current ISA
382                // variant. Under RVE this covers field > 1 (i.e. r1sc/r2sc > 1 in the spec
383                // pseudocode), which maps to x18-x23 - registers that do not exist in the E
384                // extension.
385                let r1s = Reg::from_bits(sreg_bits(r1s_bits))?;
386                let r2s = Reg::from_bits(sreg_bits(r2s_bits))?;
387
388                // funct2[6:5]: 0b11 -> CM.MVA01S, 0b01 -> CM.MVSA01, others reserved
389                match funct2 {
390                    0b11 => Some(Self::CmMva01s { rs1: r1s, rs2: r2s }),
391                    0b01 => {
392                        // CM.MVSA01 requires r1s' != r2s'
393                        if r1s_bits == r2s_bits {
394                            None?;
395                        }
396                        Some(Self::CmMvsa01 { rs1: r1s, rs2: r2s })
397                    }
398                    _ => None,
399                }
400            }
401            // funct2_12_11 values 0b00 and 0b10 are not defined by Zcmp
402            _ => None,
403        }
404    }
405
406    #[inline(always)]
407    fn alignment() -> u8 {
408        align_of::<u16>() as u8
409    }
410
411    #[inline(always)]
412    fn size(&self) -> u8 {
413        size_of::<u16>() as u8
414    }
415}
416
417#[instruction]
418impl<Reg> fmt::Display for Rv32ZcmpOnlyInstruction<Reg>
419where
420    Reg: Register,
421{
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        match self {
424            Self::CmPush { urlist, stack_adj } => {
425                write!(f, "cm.push {urlist}, -{stack_adj}")
426            }
427            Self::CmPop { urlist, stack_adj } => {
428                write!(f, "cm.pop {urlist}, {stack_adj}")
429            }
430            Self::CmPopretz { urlist, stack_adj } => {
431                write!(f, "cm.popretz {urlist}, {stack_adj}")
432            }
433            Self::CmPopret { urlist, stack_adj } => {
434                write!(f, "cm.popret {urlist}, {stack_adj}")
435            }
436            Self::CmMva01s { rs1, rs2 } => write!(f, "cm.mva01s {rs1}, {rs2}"),
437            Self::CmMvsa01 { rs1, rs2 } => write!(f, "cm.mvsa01 {rs1}, {rs2}"),
438        }
439    }
440}