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