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}