Skip to main content

ab_riscv_primitives/instructions/rv32/zce/
zcb.rs

1//! RV32 Zcb 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::Register;
10use ab_riscv_macros::instruction;
11use core::fmt;
12
13/// RISC-V RV32 Zcb compressed instruction set.
14///
15/// All register operands are prime-field (x8–x15) registers.
16#[instruction(
17    inherit = [Rv32ZcaInstruction, Rv32ZcbOnlyInstruction],
18)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Rv32ZcbInstruction<Reg> {}
21
22#[instruction]
23const impl<Reg> Instruction for Rv32ZcbInstruction<Reg>
24where
25    Reg: [const] Register<Type = u32>,
26{
27    type Reg = Reg;
28
29    #[inline(always)]
30    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
31    fn try_decode(instruction: u32) -> Option<Self> {
32        None
33    }
34
35    #[inline(always)]
36    fn alignment() -> u8 {
37        align_of::<u16>() as u8
38    }
39
40    #[inline(always)]
41    fn size(&self) -> u8 {
42        size_of::<u16>() as u8
43    }
44}
45
46#[instruction]
47impl<Reg> fmt::Display for Rv32ZcbInstruction<Reg>
48where
49    Reg: fmt::Display + Copy,
50{
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {}
53    }
54}
55
56/// Instruction that contains isolated Zcb instructions without inheriting Zca for testing purposes
57#[instruction]
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59#[rustfmt::skip]
60#[doc(hidden)]
61pub enum Rv32ZcbOnlyInstruction<Reg> {
62    // Q00 loads / stores
63    /// C.LBU  rd' = zero_extend(mem8\[rs1' + uimm])  uimm ∈ {0,1,2,3}
64    CLbu { rd: Reg, rs1: Reg, uimm: u8 },
65    /// C.LH   rd' = sign_extend(mem16\[rs1' + uimm])  uimm ∈ {0,2}
66    CLh { rd: Reg, rs1: Reg, uimm: u8 },
67    /// C.LHU  rd' = zero_extend(mem16\[rs1' + uimm])  uimm ∈ {0,2}
68    CLhu { rd: Reg, rs1: Reg, uimm: u8 },
69    /// C.SB   mem8\[rs1' + uimm] = rs2'  uimm ∈ {0,1,2,3}
70    CSb { rs1: Reg, rs2: Reg, uimm: u8 },
71    /// C.SH   mem16\[rs1' + uimm] = rs2'  uimm ∈ {0,2}
72    CSh { rs1: Reg, rs2: Reg, uimm: u8 },
73
74    // Q01 unary bit-manipulation
75    /// C.ZEXT.B  rd' = rd' & 0xff
76    CZextB { rd: Reg },
77    /// C.SEXT.B  rd' = sext(rd'\[7:0])  (requires Zbb)
78    #[instruction(if = [Rv32ZbbInstruction])]
79    CSextB { rd: Reg },
80    /// C.ZEXT.H  rd' = rd' & 0xffff  (requires Zbb)
81    #[instruction(if = [Rv32ZbbInstruction])]
82    CZextH { rd: Reg },
83    /// C.SEXT.H  rd' = sext(rd'\[15:0])  (requires Zbb)
84    #[instruction(if = [Rv32ZbbInstruction])]
85    CSextH { rd: Reg },
86    /// C.NOT  rd' = ~rd'
87    CNot { rd: Reg },
88
89    // Q01 binary
90    /// C.MUL  rd' = (rd' * rs2')\[31:0]  (requires M or Zmmul)
91    #[instruction(
92        if = [Rv32MInstruction],
93        if = [Rv32ZmmulInstruction]
94    )]
95    CMul { rd: Reg, rs2: Reg },
96}
97
98#[instruction]
99const impl<Reg> Instruction for Rv32ZcbOnlyInstruction<Reg>
100where
101    Reg: [const] Register<Type = u32>,
102{
103    type Reg = Reg;
104
105    #[inline(always)]
106    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
107    fn try_decode(instruction: u32) -> Option<Self> {
108        /// Map a 3-bit "prime" register field to an absolute register number
109        #[inline(always)]
110        const fn prime_reg_bits(bits: u8) -> u8 {
111            bits + 8
112        }
113
114        let inst = instruction as u16;
115        let quadrant = inst & 0b11;
116        let funct3 = ((inst >> 13) & 0b111) as u8;
117
118        match quadrant {
119            // Q00 funct3=100: C.LBU / C.LHU / C.LH / C.SB / C.SH
120            0b00 if funct3 == 0b100 => {
121                let sub = ((inst >> 10) & 0b111) as u8;
122                let rs1_bits = prime_reg_bits(((inst >> 7) & 0b111) as u8);
123                let rd_rs2_bits = prime_reg_bits(((inst >> 2) & 0b111) as u8);
124
125                match sub {
126                    // C.LBU  uimm[1]=inst[5], uimm[0]=inst[6]
127                    0b000 => {
128                        let uimm = ((((inst >> 5) & 1) << 1) | ((inst >> 6) & 1)) as u8;
129                        let rs1 = Reg::from_bits(rs1_bits)?;
130                        let rd = Reg::from_bits(rd_rs2_bits)?;
131                        Some(Self::CLbu { rd, rs1, uimm })
132                    }
133                    // C.LHU (funct1=inst[6]=0) / C.LH (funct1=inst[6]=1)
134                    // uimm[1]=inst[5], uimm[0]=0 (halfword aligned)
135                    0b001 => {
136                        let funct1 = ((inst >> 6) & 1) as u8;
137                        let uimm = (((inst >> 5) & 1) as u8) << 1;
138                        let rs1 = Reg::from_bits(rs1_bits)?;
139                        let rd = Reg::from_bits(rd_rs2_bits)?;
140                        if funct1 == 0 {
141                            Some(Self::CLhu { rd, rs1, uimm })
142                        } else {
143                            Some(Self::CLh { rd, rs1, uimm })
144                        }
145                    }
146                    // C.SB  uimm[1]=inst[5], uimm[0]=inst[6]
147                    0b010 => {
148                        let uimm = ((((inst >> 5) & 1) << 1) | ((inst >> 6) & 1)) as u8;
149                        let rs1 = Reg::from_bits(rs1_bits)?;
150                        let rs2 = Reg::from_bits(rd_rs2_bits)?;
151                        Some(Self::CSb { rs1, rs2, uimm })
152                    }
153                    // C.SH  funct1=inst[6]=0, uimm[1]=inst[5]
154                    0b011 => {
155                        if ((inst >> 6) & 1) != 0 {
156                            None?;
157                        }
158                        let uimm = (((inst >> 5) & 1) as u8) << 1;
159                        let rs1 = Reg::from_bits(rs1_bits)?;
160                        let rs2 = Reg::from_bits(rd_rs2_bits)?;
161                        Some(Self::CSh { rs1, rs2, uimm })
162                    }
163                    _ => None,
164                }
165            }
166
167            // Q01 funct3=100, funct2[11:10]=11, bit12=1: unary ops and C.MUL
168            //
169            // Encoding layout (per ratified Zcb spec):
170            //   funct2b = inst[6:5]
171            //   0b11 => unary ops, sub-op = inst[4:2]
172            //   0b10 => C.MUL, rs2' = inst[4:2]
173            //   0b00, 0b01 => reserved
174            0b01 if funct3 == 0b100 => {
175                let funct2_11_10 = ((inst >> 10) & 0b11) as u8;
176                let bit12 = (inst >> 12) & 1;
177
178                if funct2_11_10 != 0b11 || bit12 == 0 {
179                    None?;
180                }
181
182                let rd_rs1_bits = prime_reg_bits(((inst >> 7) & 0b111) as u8);
183                let funct2b = ((inst >> 5) & 0b11) as u8;
184                let rs2_sub = ((inst >> 2) & 0b111) as u8;
185
186                match funct2b {
187                    // Unary ops: funct2b=0b11, sub-op in inst[4:2]
188                    0b11 => {
189                        let rd = Reg::from_bits(rd_rs1_bits)?;
190                        match rs2_sub {
191                            0b000 => Some(Self::CZextB { rd }),
192                            0b001 => Some(Self::CSextB { rd }),
193                            0b010 => Some(Self::CZextH { rd }),
194                            0b011 => Some(Self::CSextH { rd }),
195                            0b101 => Some(Self::CNot { rd }),
196                            // 0b100, 0b110, 0b111 reserved
197                            _ => None,
198                        }
199                    }
200                    // C.MUL: funct2b=0b10, rs2' = inst[4:2]
201                    0b10 => {
202                        let rd = Reg::from_bits(rd_rs1_bits)?;
203                        let rs2 = Reg::from_bits(prime_reg_bits(rs2_sub))?;
204                        Some(Self::CMul { rd, rs2 })
205                    }
206                    // funct2b=0b00 and funct2b=0b01 are reserved in Zcb
207                    _ => None,
208                }
209            }
210            _ => None,
211        }
212    }
213
214    #[inline(always)]
215    fn alignment() -> u8 {
216        align_of::<u16>() as u8
217    }
218
219    #[inline(always)]
220    fn size(&self) -> u8 {
221        size_of::<u16>() as u8
222    }
223}
224
225#[instruction]
226impl<Reg> fmt::Display for Rv32ZcbOnlyInstruction<Reg>
227where
228    Reg: fmt::Display + Copy,
229{
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        match self {
232            Self::CLbu { rd, rs1, uimm } => write!(f, "c.lbu {rd}, {uimm}({rs1})"),
233            Self::CLh { rd, rs1, uimm } => write!(f, "c.lh {rd}, {uimm}({rs1})"),
234            Self::CLhu { rd, rs1, uimm } => write!(f, "c.lhu {rd}, {uimm}({rs1})"),
235            Self::CSb { rs1, rs2, uimm } => write!(f, "c.sb {rs2}, {uimm}({rs1})"),
236            Self::CSh { rs1, rs2, uimm } => write!(f, "c.sh {rs2}, {uimm}({rs1})"),
237            Self::CZextB { rd } => write!(f, "c.zext.b {rd}"),
238            Self::CSextB { rd } => write!(f, "c.sext.b {rd}"),
239            Self::CZextH { rd } => write!(f, "c.zext.h {rd}"),
240            Self::CSextH { rd } => write!(f, "c.sext.h {rd}"),
241            Self::CNot { rd } => write!(f, "c.not {rd}"),
242            Self::CMul { rd, rs2 } => write!(f, "c.mul {rd}, {rs2}"),
243        }
244    }
245}