Skip to main content

ab_riscv_primitives/instructions/rv64/zce/
zcb.rs

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