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