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