sp1_core_executor/
opcode.rs

1//! Opcodes for the SP1 zkVM.
2
3use std::fmt::Display;
4
5use enum_map::Enum;
6use p3_field::Field;
7use serde::{Deserialize, Serialize};
8
9/// An opcode (short for "operation code") specifies the operation to be performed by the processor.
10///
11/// In the context of the RISC-V ISA, an opcode specifies which operation (i.e., addition,
12/// subtraction, multiplication, etc.) to perform on up to three operands such as registers,
13/// immediates, or memory addresses.
14///
15/// While the SP1 zkVM targets the RISC-V ISA, it uses a custom instruction encoding that uses
16/// a different set of opcodes. The main difference is that the SP1 zkVM encodes register
17/// operations and immediate operations as the same opcode. For example, the RISC-V opcodes ADD and
18/// ADDI both become ADD inside the SP1 zkVM. We utilize flags inside the instruction itself to
19/// distinguish between the two.
20///
21/// Refer to the "RV32I Reference Card" [here](https://github.com/johnwinans/rvalp/releases) for
22/// more details.
23#[allow(non_camel_case_types)]
24#[derive(
25    Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord, Enum,
26)]
27#[repr(u8)]
28pub enum Opcode {
29    /// rd ← rs1 + rs2, pc ← pc + 4
30    ADD = 0,
31    /// rd ← rs1 - rs2, pc ← pc + 4
32    SUB = 1,
33    /// rd ← rs1 ^ rs2, pc ← pc + 4
34    XOR = 2,
35    /// rd ← rs1 | rs2, pc ← pc + 4
36    OR = 3,
37    /// rd ← rs1 & rs2, pc ← pc + 4
38    AND = 4,
39    /// rd ← rs1 << rs2, pc ← pc + 4
40    SLL = 5,
41    /// rd ← rs1 >> rs2 (logical), pc ← pc + 4
42    SRL = 6,
43    /// rd ← rs1 >> rs2 (arithmetic), pc ← pc + 4
44    SRA = 7,
45    /// rd ← (rs1 < rs2) ? 1 : 0 (signed), pc ← pc + 4
46    SLT = 8,
47    /// rd ← (rs1 < rs2) ? 1 : 0 (unsigned), pc ← pc + 4
48    SLTU = 9,
49    /// rd ← rs1 * rs2 (signed), pc ← pc + 4
50    MUL = 10,
51    /// rd ← rs1 * rs2 (half), pc ← pc + 4
52    MULH = 11,
53    /// rd ← rs1 * rs2 (half unsigned), pc ← pc + 4
54    MULHU = 12,
55    /// rd ← rs1 * rs2 (half signed unsigned), pc ← pc + 4
56    MULHSU = 13,
57    /// rd ← rs1 / rs2 (signed), pc ← pc + 4
58    DIV = 14,
59    /// rd ← rs1 / rs2 (unsigned), pc ← pc + 4
60    DIVU = 15,
61    /// rd ← rs1 % rs2 (signed), pc ← pc + 4
62    REM = 16,
63    /// rd ← rs1 % rs2 (unsigned), pc ← pc + 4
64    REMU = 17,
65    /// rd ← sx(m8(rs1 + imm)), pc ← pc + 4
66    LB = 18,
67    /// rd ← sx(m16(rs1 + imm)), pc ← pc + 4
68    LH = 19,
69    /// rd ← sx(m32(rs1 + imm)), pc ← pc + 4
70    LW = 20,
71    /// rd ← zx(m8(rs1 + imm)), pc ← pc + 4
72    LBU = 21,
73    /// rd ← zx(m16(rs1 + imm)), pc ← pc + 4
74    LHU = 22,
75    /// m8(rs1 + imm) ← rs2[7:0], pc ← pc + 4
76    SB = 23,
77    /// m16(rs1 + imm) ← rs2[15:0], pc ← pc + 4
78    SH = 24,
79    /// m32(rs1 + imm) ← rs2[31:0], pc ← pc + 4
80    SW = 25,
81    /// pc ← pc + ((rs1 == rs2) ? imm : 4)
82    BEQ = 26,
83    /// pc ← pc + ((rs1 != rs2) ? imm : 4)
84    BNE = 27,
85    /// pc ← pc + ((rs1 < rs2) ? imm : 4) (signed)
86    BLT = 28,
87    /// pc ← pc + ((rs1 >= rs2) ? imm : 4) (signed)
88    BGE = 29,
89    /// pc ← pc + ((rs1 < rs2) ? imm : 4) (unsigned)
90    BLTU = 30,
91    /// pc ← pc + ((rs1 >= rs2) ? imm : 4) (unsigned)
92    BGEU = 31,
93    /// rd ← pc + 4, pc ← pc + imm
94    JAL = 32,
95    /// rd ← pc + 4, pc ← (rs1 + imm) & ∼1
96    JALR = 33,
97    /// rd ← pc + imm, pc ← pc + 4
98    AUIPC = 34,
99    /// Transfer control to the operating system.
100    ECALL = 35,
101    /// Transfer control to the debugger.
102    EBREAK = 36,
103    /// Unimplemented instruction.
104    UNIMP = 37,
105}
106/// Byte Opcode.
107///
108/// This represents a basic operation that can be performed on a byte. Usually, these operations
109/// are performed via lookup tables on that iterate over the domain of two 8-bit values. The
110/// operations include both bitwise operations (AND, OR, XOR) as well as basic arithmetic.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
112#[allow(clippy::upper_case_acronyms)]
113pub enum ByteOpcode {
114    /// Bitwise AND.
115    AND = 0,
116    /// Bitwise OR.
117    OR = 1,
118    /// Bitwise XOR.
119    XOR = 2,
120    /// Shift Left Logical.
121    SLL = 3,
122    /// Unsigned 8-bit Range Check.
123    U8Range = 4,
124    /// Shift Right with Carry.
125    ShrCarry = 5,
126    /// Unsigned Less Than.
127    LTU = 6,
128    /// Most Significant Bit.
129    MSB = 7,
130    /// Unsigned 16-bit Range Check.
131    U16Range = 8,
132}
133
134impl Opcode {
135    /// Get the mnemonic for the opcode.
136    #[must_use]
137    pub const fn mnemonic(&self) -> &str {
138        match self {
139            Opcode::ADD => "add",
140            Opcode::SUB => "sub",
141            Opcode::XOR => "xor",
142            Opcode::OR => "or",
143            Opcode::AND => "and",
144            Opcode::SLL => "sll",
145            Opcode::SRL => "srl",
146            Opcode::SRA => "sra",
147            Opcode::SLT => "slt",
148            Opcode::SLTU => "sltu",
149            Opcode::LB => "lb",
150            Opcode::LH => "lh",
151            Opcode::LW => "lw",
152            Opcode::LBU => "lbu",
153            Opcode::LHU => "lhu",
154            Opcode::SB => "sb",
155            Opcode::SH => "sh",
156            Opcode::SW => "sw",
157            Opcode::BEQ => "beq",
158            Opcode::BNE => "bne",
159            Opcode::BLT => "blt",
160            Opcode::BGE => "bge",
161            Opcode::BLTU => "bltu",
162            Opcode::BGEU => "bgeu",
163            Opcode::JAL => "jal",
164            Opcode::JALR => "jalr",
165            Opcode::AUIPC => "auipc",
166            Opcode::ECALL => "ecall",
167            Opcode::EBREAK => "ebreak",
168            Opcode::MUL => "mul",
169            Opcode::MULH => "mulh",
170            Opcode::MULHU => "mulhu",
171            Opcode::MULHSU => "mulhsu",
172            Opcode::DIV => "div",
173            Opcode::DIVU => "divu",
174            Opcode::REM => "rem",
175            Opcode::REMU => "remu",
176            Opcode::UNIMP => "unimp",
177        }
178    }
179
180    /// Convert the opcode to a field element.
181    #[must_use]
182    pub fn as_field<F: Field>(self) -> F {
183        F::from_canonical_u32(self as u32)
184    }
185}
186
187impl Display for Opcode {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        f.write_str(self.mnemonic())
190    }
191}