Skip to main content

sp1_core_executor/
opcode.rs

1//! Opcodes for the SP1 zkVM.
2
3use std::fmt::Display;
4
5use deepsize2::DeepSizeOf;
6use enum_map::Enum;
7use rrs_lib::instruction_formats::{
8    OPCODE_AUIPC, OPCODE_BRANCH, OPCODE_JAL, OPCODE_JALR, OPCODE_LOAD, OPCODE_LUI, OPCODE_OP,
9    OPCODE_OP_32, OPCODE_OP_IMM, OPCODE_OP_IMM_32, OPCODE_STORE, OPCODE_SYSTEM,
10};
11use serde::{Deserialize, Serialize};
12use slop_algebra::Field;
13
14use crate::InstructionType;
15
16/// An opcode (short for "operation code") specifies the operation to be performed by the processor.
17///
18/// In the context of the RISC-V ISA, an opcode specifies which operation (i.e., addition,
19/// subtraction, multiplication, etc.) to perform on up to three operands such as registers,
20/// immediates, or memory addresses.
21///
22/// While the SP1 zkVM targets the RISC-V ISA, it uses a custom instruction encoding that uses
23/// a different set of opcodes. The main difference is that the SP1 zkVM encodes register
24/// operations and immediate operations as the same opcode. For example, the RISC-V opcodes ADD and
25/// ADDI both become ADD inside the SP1 zkVM. We utilize flags inside the instruction itself to
26/// distinguish between the two.
27///
28/// Refer to the "RV32I Reference Card" [here](https://github.com/johnwinans/rvalp/releases) for
29/// more details.
30#[allow(non_camel_case_types)]
31#[derive(
32    Debug,
33    Clone,
34    Copy,
35    PartialEq,
36    Eq,
37    Hash,
38    Serialize,
39    Deserialize,
40    PartialOrd,
41    Ord,
42    Enum,
43    DeepSizeOf,
44)]
45#[repr(u8)]
46pub enum Opcode {
47    /// rd ← rs1 + rs2, pc ← pc + 4
48    ADD = 0,
49    /// rd ← rs1 + imm, pc ← pc + 4
50    ADDI = 1,
51    /// rd ← rs1 - rs2, pc ← pc + 4
52    SUB = 2,
53    /// rd ← rs1 ^ rs2, pc ← pc + 4
54    XOR = 3,
55    /// rd ← rs1 | rs2, pc ← pc + 4
56    OR = 4,
57    /// rd ← rs1 & rs2, pc ← pc + 4
58    AND = 5,
59    /// rd ← rs1 << rs2, pc ← pc + 4
60    SLL = 6,
61    /// rd ← rs1 >> rs2 (logical), pc ← pc + 4
62    SRL = 7,
63    /// rd ← rs1 >> rs2 (arithmetic), pc ← pc + 4
64    SRA = 8,
65    /// rd ← (rs1 < rs2) ? 1 : 0 (signed), pc ← pc + 4
66    SLT = 9,
67    /// rd ← (rs1 < rs2) ? 1 : 0 (unsigned), pc ← pc + 4
68    SLTU = 10,
69    /// rd ← rs1 * rs2 (signed), pc ← pc + 4
70    MUL = 11,
71    /// rd ← rs1 * rs2 (half), pc ← pc + 4
72    MULH = 12,
73    /// rd ← rs1 * rs2 (half unsigned), pc ← pc + 4
74    MULHU = 13,
75    /// rd ← rs1 * rs2 (half signed unsigned), pc ← pc + 4
76    MULHSU = 14,
77    /// rd ← rs1 / rs2 (signed), pc ← pc + 4
78    DIV = 15,
79    /// rd ← rs1 / rs2 (unsigned), pc ← pc + 4
80    DIVU = 16,
81    /// rd ← rs1 % rs2 (signed), pc ← pc + 4
82    REM = 17,
83    /// rd ← rs1 % rs2 (unsigned), pc ← pc + 4
84    REMU = 18,
85    /// rd ← rs1 + rs2, pc ← pc + 4
86    ADDW = 19,
87    /// rd ← rs1 - rs2, pc ← pc + 4
88    SUBW = 20,
89    /// rd ← rs1 << rs2, pc ← pc + 4
90    SLLW = 21,
91    /// rd ← rs1 >> rs2 (logical), pc ← pc + 4
92    SRLW = 22,
93    /// rd ← rs1 >> rs2 (arithmetic), pc ← pc + 4
94    SRAW = 23,
95    /// rd ← rs1 + imm, pc ← pc + 4
96    MULW = 24,
97    /// rd ← rs1 / rs2 (signed), pc ← pc + 4
98    DIVW = 25,
99    /// rd ← rs1 / rs2 (unsigned), pc ← pc + 4
100    DIVUW = 26,
101    /// rd ← rs1 % rs2 (signed), pc ← pc + 4
102    REMW = 27,
103    /// rd ← rs1 % rs2 (unsigned), pc ← pc + 4
104    REMUW = 28,
105    /// rd ← sx(m8(rs1 + imm)), pc ← pc + 4
106    LB = 29,
107    /// rd ← sx(m16(rs1 + imm)), pc ← pc + 4
108    LH = 30,
109    /// rd ← sx(m32(rs1 + imm)), pc ← pc + 4
110    LW = 31,
111    /// rd ← zx(m8(rs1 + imm)), pc ← pc + 4
112    LBU = 32,
113    /// rd ← zx(m16(rs1 + imm)), pc ← pc + 4
114    LHU = 33,
115    /// rd ← sx(m32(rs1 + imm)), pc ← pc + 4
116    LWU = 34,
117    /// rd ← sx(m8(rs1 + imm)), pc ← pc + 4
118    LD = 35,
119    /// m8(rs1 + imm) ← rs2[7:0], pc ← pc + 4
120    SB = 36,
121    /// m16(rs1 + imm) ← rs2[15:0], pc ← pc + 4
122    SH = 37,
123    /// m32(rs1 + imm) ← rs2[31:0], pc ← pc + 4
124    SW = 38,
125    /// m8(rs1 + imm) ← rs2[7:0], pc ← pc + 4
126    SD = 39,
127    /// pc ← pc + ((rs1 == rs2) ? imm : 4)
128    BEQ = 40,
129    /// pc ← pc + ((rs1 != rs2) ? imm : 4)
130    BNE = 41,
131    /// pc ← pc + ((rs1 < rs2) ? imm : 4) (signed)
132    BLT = 42,
133    /// pc ← pc + ((rs1 >= rs2) ? imm : 4) (signed)
134    BGE = 43,
135    /// pc ← pc + ((rs1 < rs2) ? imm : 4) (unsigned)
136    BLTU = 44,
137    /// pc ← pc + ((rs1 >= rs2) ? imm : 4) (unsigned)
138    BGEU = 45,
139    /// rd ← pc + 4, pc ← pc + imm
140    JAL = 46,
141    /// rd ← pc + 4, pc ← (rs1 + imm) & ∼1
142    JALR = 47,
143    /// rd ← pc + imm, pc ← pc + 4
144    AUIPC = 48,
145    /// rd ← imm, pc ← pc + 4
146    LUI = 49,
147    /// Transfer control to the ecall handler.
148    ECALL = 50,
149    /// Transfer control to the debugger.
150    EBREAK = 51,
151    /// Unimplemented instruction.
152    UNIMP = 52,
153}
154/// Byte Opcode.
155///
156/// This represents a basic operation that can be performed on a byte. Usually, these operations
157/// are performed via lookup tables on that iterate over the domain of two 8-bit values. The
158/// operations include both bitwise operations (AND, OR, XOR) as well as basic arithmetic.
159#[derive(
160    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, DeepSizeOf,
161)]
162#[allow(clippy::upper_case_acronyms)]
163pub enum ByteOpcode {
164    /// Bitwise AND.
165    AND = 0,
166    /// Bitwise OR.
167    OR = 1,
168    /// Bitwise XOR.
169    XOR = 2,
170    /// Unsigned 8-bit Range Check.
171    U8Range = 3,
172    /// Unsigned Less Than.
173    LTU = 4,
174    /// Most Significant Bit.
175    MSB = 5,
176    /// Range Check.
177    Range = 6,
178}
179
180impl Opcode {
181    /// Get the mnemonic for the opcode.
182    #[must_use]
183    pub const fn mnemonic(&self) -> &str {
184        match self {
185            Opcode::ADD => "add",
186            Opcode::ADDI => "addi",
187            Opcode::SUB => "sub",
188            Opcode::XOR => "xor",
189            Opcode::OR => "or",
190            Opcode::AND => "and",
191            Opcode::SLL => "sll",
192            Opcode::SRL => "srl",
193            Opcode::SRA => "sra",
194            Opcode::SLT => "slt",
195            Opcode::SLTU => "sltu",
196            Opcode::LB => "lb",
197            Opcode::LH => "lh",
198            Opcode::LW => "lw",
199            Opcode::LBU => "lbu",
200            Opcode::LHU => "lhu",
201            Opcode::SB => "sb",
202            Opcode::SH => "sh",
203            Opcode::SW => "sw",
204            Opcode::BEQ => "beq",
205            Opcode::BNE => "bne",
206            Opcode::BLT => "blt",
207            Opcode::BGE => "bge",
208            Opcode::BLTU => "bltu",
209            Opcode::BGEU => "bgeu",
210            Opcode::JAL => "jal",
211            Opcode::JALR => "jalr",
212            Opcode::AUIPC => "auipc",
213            Opcode::LUI => "lui",
214            Opcode::ECALL => "ecall",
215            Opcode::EBREAK => "ebreak",
216            Opcode::MUL => "mul",
217            Opcode::MULH => "mulh",
218            Opcode::MULHU => "mulhu",
219            Opcode::MULHSU => "mulhsu",
220            Opcode::DIV => "div",
221            Opcode::DIVU => "divu",
222            Opcode::REM => "rem",
223            Opcode::REMU => "remu",
224            Opcode::ADDW => "addw",
225            Opcode::SUBW => "subw",
226            Opcode::SLLW => "sllw",
227            Opcode::SRLW => "srlw",
228            Opcode::SRAW => "sraw",
229            Opcode::LWU => "lwu",
230            Opcode::LD => "ld",
231            Opcode::SD => "sd",
232            Opcode::MULW => "mulw",
233            Opcode::DIVW => "divw",
234            Opcode::DIVUW => "divuw",
235            Opcode::REMW => "remw",
236            Opcode::REMUW => "remuw",
237            Opcode::UNIMP => "unimp",
238        }
239    }
240
241    /// Convert the opcode to a field element.
242    #[must_use]
243    pub fn as_field<F: Field>(self) -> F {
244        F::from_canonical_u32(self as u32)
245    }
246
247    /// Returns the funct3 field for the opcode.
248    #[must_use]
249    #[allow(clippy::match_same_arms)]
250    pub fn funct3(self: Opcode) -> Option<u8> {
251        Some(match self {
252            // R-type and I-type ALU
253            Opcode::ADD | Opcode::SUB | Opcode::ADDI => 0b000,
254            Opcode::SLL => 0b001,
255            Opcode::SLT => 0b010,
256            Opcode::SLTU => 0b011,
257            Opcode::XOR => 0b100,
258            Opcode::SRL | Opcode::SRA => 0b101,
259            Opcode::OR => 0b110,
260            Opcode::AND => 0b111,
261            Opcode::ADDW => 0b000,
262            Opcode::SUBW => 0b000,
263            Opcode::SLLW => 0b001,
264            Opcode::SRLW => 0b101,
265            Opcode::SRAW => 0b101,
266            Opcode::LWU => 0b110,
267            Opcode::LD => 0b011,
268            Opcode::SD => 0b011,
269            Opcode::MULW => 0b000,
270            Opcode::DIVW => 0b100,
271            Opcode::DIVUW => 0b101,
272            Opcode::REMW => 0b110,
273            Opcode::REMUW => 0b111,
274
275            // M-extension (same funct3 as ALU)
276            Opcode::MUL => 0b000,
277            Opcode::MULH => 0b001,
278            Opcode::MULHSU => 0b010,
279            Opcode::MULHU => 0b011,
280            Opcode::DIV => 0b100,
281            Opcode::DIVU => 0b101,
282            Opcode::REM => 0b110,
283            Opcode::REMU => 0b111,
284
285            // Loads
286            Opcode::LB => 0b000,
287            Opcode::LH => 0b001,
288            Opcode::LW => 0b010,
289            Opcode::LBU => 0b100,
290            Opcode::LHU => 0b101,
291
292            // Stores
293            Opcode::SB => 0b000,
294            Opcode::SH => 0b001,
295            Opcode::SW => 0b010,
296
297            // Branches
298            Opcode::BEQ => 0b000,
299            Opcode::BNE => 0b001,
300            Opcode::BLT => 0b100,
301            Opcode::BGE => 0b101,
302            Opcode::BLTU => 0b110,
303            Opcode::BGEU => 0b111,
304
305            // JAL/JALR
306            Opcode::JALR => 0b000, // JALR has funct3 = 000
307
308            // System instructions (ECALL, EBREAK, MRET): fixed encoding
309            Opcode::ECALL | Opcode::EBREAK => 0b000,
310
311            // Instructions without funct3 field
312            Opcode::JAL | Opcode::AUIPC | Opcode::LUI | Opcode::UNIMP => return None,
313        })
314    }
315
316    /// Returns the funct7 field for the opcode.
317    #[must_use]
318    #[allow(clippy::match_same_arms)]
319    pub fn funct7(self: Opcode) -> Option<u8> {
320        use Opcode::{
321            ADD, ADDI, ADDW, AND, AUIPC, BEQ, BGE, BGEU, BLT, BLTU, BNE, DIV, DIVU, DIVUW, DIVW,
322            EBREAK, ECALL, JAL, JALR, LB, LBU, LD, LH, LHU, LUI, LW, LWU, MUL, MULH, MULHSU, MULHU,
323            MULW, OR, REM, REMU, REMUW, REMW, SB, SD, SH, SLL, SLLW, SLT, SLTU, SRA, SRAW, SRL,
324            SRLW, SUB, SUBW, SW, UNIMP, XOR,
325        };
326        Some(match self {
327            ADD | SLL | SLT | SLTU | XOR | SRL | OR | AND | ADDW | SLLW | SRLW => 0b0000000,
328            SUB | SRA | SUBW | SRAW => 0b0100000,
329            MUL | MULH | MULHSU | MULHU | DIV | DIVU | REM | REMU | MULW | DIVW | DIVUW | REMW
330            | REMUW => 0b0000001,
331            ECALL | EBREAK => 0b0000000,
332            ADDI | LB | LH | LW | LBU | LHU | SB | SH | SW | BEQ | BNE | BLT | BGE | BLTU
333            | BGEU | JAL | JALR | AUIPC | LUI | UNIMP | LWU | LD | SD => return None,
334        })
335    }
336
337    /// Returns the funct12 field for the opcode.
338    #[must_use]
339    pub fn funct12(self: Opcode) -> Option<u32> {
340        use Opcode::ECALL;
341        Some(match self {
342            ECALL => 0x000,
343            _ => return None,
344        })
345    }
346
347    #[must_use]
348    /// Returns the base opcode for the opcode.
349    pub fn base_opcode(self: Opcode) -> (u32, Option<u32>) {
350        match self {
351            Opcode::SLL
352            | Opcode::SRL
353            | Opcode::SRA
354            | Opcode::XOR
355            | Opcode::OR
356            | Opcode::AND
357            | Opcode::SLT
358            | Opcode::SLTU => (OPCODE_OP, Some(OPCODE_OP_IMM)),
359
360            Opcode::ADD
361            | Opcode::SUB
362            | Opcode::MUL
363            | Opcode::MULH
364            | Opcode::MULHU
365            | Opcode::MULHSU
366            | Opcode::DIV
367            | Opcode::DIVU
368            | Opcode::REM
369            | Opcode::REMU => (OPCODE_OP, Some(OPCODE_OP)),
370
371            Opcode::ADDI => (OPCODE_OP_IMM, Some(OPCODE_OP_IMM)),
372
373            Opcode::ECALL => (OPCODE_SYSTEM, None),
374
375            Opcode::JALR => (OPCODE_JALR, Some(OPCODE_JALR)),
376
377            Opcode::LB
378            | Opcode::LH
379            | Opcode::LW
380            | Opcode::LBU
381            | Opcode::LHU
382            | Opcode::LWU
383            | Opcode::LD => (OPCODE_LOAD, Some(OPCODE_LOAD)),
384
385            Opcode::SB | Opcode::SH | Opcode::SW | Opcode::SD => (OPCODE_STORE, Some(OPCODE_STORE)),
386
387            Opcode::BEQ | Opcode::BNE | Opcode::BLT | Opcode::BGE | Opcode::BLTU | Opcode::BGEU => {
388                (OPCODE_BRANCH, Some(OPCODE_BRANCH))
389            }
390
391            Opcode::AUIPC => (OPCODE_AUIPC, Some(OPCODE_AUIPC)),
392
393            Opcode::LUI => (OPCODE_LUI, Some(OPCODE_LUI)),
394
395            Opcode::JAL => (OPCODE_JAL, Some(OPCODE_JAL)),
396
397            // RISC-V 64-bit operations
398            Opcode::ADDW | Opcode::SLLW | Opcode::SRLW | Opcode::SRAW => {
399                (OPCODE_OP_32, Some(OPCODE_OP_IMM_32))
400            }
401
402            Opcode::SUBW
403            | Opcode::MULW
404            | Opcode::DIVW
405            | Opcode::DIVUW
406            | Opcode::REMW
407            | Opcode::REMUW => (OPCODE_OP_32, None),
408
409            _ => unreachable!("Opcode {:?} has no base opcode", self),
410        }
411    }
412
413    #[must_use]
414    /// Returns the instruction type for the opcode.
415    pub fn instruction_type(self) -> (InstructionType, Option<InstructionType>) {
416        match self {
417            Opcode::SLL | Opcode::SRL | Opcode::SRA => {
418                (InstructionType::RType, Some(InstructionType::ITypeShamt))
419            }
420
421            Opcode::SLLW | Opcode::SRLW | Opcode::SRAW => {
422                (InstructionType::RType, Some(InstructionType::ITypeShamt32))
423            }
424
425            Opcode::ADDW | Opcode::XOR | Opcode::OR | Opcode::AND | Opcode::SLT | Opcode::SLTU => {
426                (InstructionType::RType, Some(InstructionType::IType))
427            }
428
429            Opcode::ADD
430            | Opcode::SUB
431            | Opcode::SUBW
432            | Opcode::MUL
433            | Opcode::MULH
434            | Opcode::MULHU
435            | Opcode::MULHSU
436            | Opcode::DIV
437            | Opcode::DIVU
438            | Opcode::REM
439            | Opcode::REMU
440            | Opcode::MULW
441            | Opcode::DIVW
442            | Opcode::DIVUW
443            | Opcode::REMW
444            | Opcode::REMUW => (InstructionType::RType, None),
445
446            Opcode::ADDI => (InstructionType::IType, Some(InstructionType::IType)),
447
448            Opcode::ECALL => (InstructionType::ECALL, None),
449
450            Opcode::JALR
451            | Opcode::LB
452            | Opcode::LH
453            | Opcode::LW
454            | Opcode::LBU
455            | Opcode::LHU
456            | Opcode::LWU
457            | Opcode::LD => (InstructionType::IType, None),
458
459            Opcode::SB | Opcode::SH | Opcode::SW | Opcode::SD => (InstructionType::SType, None),
460
461            Opcode::BEQ | Opcode::BNE | Opcode::BLT | Opcode::BGE | Opcode::BLTU | Opcode::BGEU => {
462                (InstructionType::BType, None)
463            }
464
465            Opcode::AUIPC | Opcode::LUI => (InstructionType::UType, None),
466
467            Opcode::JAL => (InstructionType::JType, None),
468
469            _ => unreachable!("Opcode {:?} has no instruction type", self),
470        }
471    }
472}
473
474impl Display for Opcode {
475    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476        f.write_str(self.mnemonic())
477    }
478}