sp1_core_machine/cpu/air/mod.rs
1pub mod register;
2
3use core::borrow::Borrow;
4use p3_air::{Air, AirBuilder, AirBuilderWithPublicValues, BaseAir};
5use p3_field::AbstractField;
6use p3_matrix::Matrix;
7use sp1_core_executor::{ByteOpcode, DEFAULT_PC_INC};
8use sp1_stark::{
9 air::{BaseAirBuilder, PublicValues, SP1AirBuilder, SP1_PROOF_NUM_PV_ELTS},
10 Word,
11};
12
13use crate::{
14 air::{MemoryAirBuilder, SP1CoreAirBuilder},
15 cpu::{
16 columns::{CpuCols, NUM_CPU_COLS},
17 CpuChip,
18 },
19};
20
21impl<AB> Air<AB> for CpuChip
22where
23 AB: SP1CoreAirBuilder + AirBuilderWithPublicValues,
24 AB::Var: Sized,
25{
26 #[inline(never)]
27 fn eval(&self, builder: &mut AB) {
28 let main = builder.main();
29 let (local, next) = (main.row_slice(0), main.row_slice(1));
30 let local: &CpuCols<AB::Var> = (*local).borrow();
31 let next: &CpuCols<AB::Var> = (*next).borrow();
32
33 let public_values_slice: [AB::PublicVar; SP1_PROOF_NUM_PV_ELTS] =
34 core::array::from_fn(|i| builder.public_values()[i]);
35 let public_values: &PublicValues<Word<AB::PublicVar>, AB::PublicVar> =
36 public_values_slice.as_slice().borrow();
37
38 // We represent the `clk` with a 16 bit limb and a 8 bit limb.
39 // The range checks for these limbs are done in `eval_shard_clk`.
40 let clk =
41 AB::Expr::from_canonical_u32(1u32 << 16) * local.clk_8bit_limb + local.clk_16bit_limb;
42
43 // Program constraints.
44 // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
45 // The `pc` and `instruction` is taken from the `ProgramChip`, where these are preprocessed.
46 builder.send_program(local.pc, local.instruction, local.is_real);
47
48 // Register constraints.
49 self.eval_registers::<AB>(builder, local, clk.clone());
50
51 // Assert the shard and clk to send. Only the memory and syscall instructions need the
52 // actual shard and clk values for memory access evals.
53 // SAFETY: The usage of `builder.if_else` requires `is_memory + is_syscall` to be boolean.
54 // The correctness of `is_memory` and `is_syscall` will be checked in the opcode specific
55 // chips. In these correct cases, `is_memory + is_syscall` will be always boolean.
56 let expected_shard_to_send =
57 builder.if_else(local.is_memory + local.is_syscall, local.shard, AB::Expr::zero());
58 let expected_clk_to_send =
59 builder.if_else(local.is_memory + local.is_syscall, clk.clone(), AB::Expr::zero());
60 builder.when(local.is_real).assert_eq(local.shard_to_send, expected_shard_to_send);
61 builder.when(local.is_real).assert_eq(local.clk_to_send, expected_clk_to_send);
62
63 // Send the instruction.
64 // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
65 // The `shard`, `clk`, `pc` are constrained throughout the CpuChip.
66 // The `local.instruction.opcode`, `local.instruction.op_a_0` are from the ProgramChip.
67 // The `local.op_b_val()` and `local.op_c_val()` are constrained in `eval_registers` in the
68 // CpuChip. Therefore, opcode specific chips that will receive this instruction need
69 // to the following.
70 // - For an instruction with a valid opcode, exactly one opcode specific chip can receive
71 // the instruction.
72 // - The `next_pc`, `num_extra_cycles`, `op_a_val`, `op_a_immutable`, `is_memory`,
73 // `is_syscall`, `is_halt` are constrained correctly.
74 // Note that in this case, `shard_to_send` and `clk_to_send` will be correctly constrained
75 // as well. If `instruction.op_a_0 == 1`, then `eval_registers` enforces `op_a_val()
76 // == 0`. Therefore, in this case, `op_a_val` doesn't need to be constrained in the
77 // opcode specific chips.
78 builder.send_instruction(
79 local.shard_to_send,
80 local.clk_to_send,
81 local.pc,
82 local.next_pc,
83 local.num_extra_cycles,
84 local.instruction.opcode,
85 local.op_a_val(),
86 local.op_b_val(),
87 local.op_c_val(),
88 local.instruction.op_a_0,
89 local.op_a_immutable,
90 local.is_memory,
91 local.is_syscall,
92 local.is_halt,
93 local.is_real,
94 );
95
96 // Check that the shard and clk is updated correctly.
97 self.eval_shard_clk(builder, local, next, public_values, clk.clone());
98
99 // Check that the pc is updated correctly.
100 self.eval_pc(builder, local, next, public_values);
101
102 // Check that the is_real flag is correct.
103 self.eval_is_real(builder, local, next);
104
105 // Check that when `is_real=0` that all flags that send interactions are zero.
106 let not_real = AB::Expr::one() - local.is_real;
107 builder.when(not_real.clone()).assert_zero(AB::Expr::one() - local.instruction.imm_b);
108 builder.when(not_real.clone()).assert_zero(AB::Expr::one() - local.instruction.imm_c);
109 builder.when(not_real.clone()).assert_zero(AB::Expr::one() - local.is_syscall);
110 }
111}
112
113impl CpuChip {
114 /// Constraints related to the shard and clk.
115 ///
116 /// This method ensures that all of the shard values are the same and that the clk starts at 0
117 /// and is transitioned appropriately. It will also check that shard values are within 16 bits
118 /// and clk values are within 24 bits. Those range checks are needed for the memory access
119 /// timestamp check, which assumes those values are within 2^24. See
120 /// [`MemoryAirBuilder::verify_mem_access_ts`].
121 pub(crate) fn eval_shard_clk<AB: SP1AirBuilder>(
122 &self,
123 builder: &mut AB,
124 local: &CpuCols<AB::Var>,
125 next: &CpuCols<AB::Var>,
126 public_values: &PublicValues<Word<AB::PublicVar>, AB::PublicVar>,
127 clk: AB::Expr,
128 ) {
129 // Verify the public value's shard.
130 builder.when(local.is_real).assert_eq(public_values.execution_shard, local.shard);
131
132 // Verify that all shard values are the same.
133 builder.when_transition().when(next.is_real).assert_eq(local.shard, next.shard);
134
135 // Verify that the shard value is within 16 bits.
136 // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
137 builder.send_byte(
138 AB::Expr::from_canonical_u8(ByteOpcode::U16Range as u8),
139 local.shard,
140 AB::Expr::zero(),
141 AB::Expr::zero(),
142 local.is_real,
143 );
144
145 // Verify that the first row has a clk value of 0.
146 builder.when_first_row().assert_zero(clk.clone());
147
148 // We already assert that `local.clk < 2^24`. `num_extra_cycles` is an entry of a word and
149 // therefore less than `2^8`, this means that the sum cannot overflow in a 31 bit field.
150 // The default clk increment is also `4`, equal to `DEFAULT_PC_INC`.
151 let expected_next_clk =
152 clk.clone() + AB::Expr::from_canonical_u32(DEFAULT_PC_INC) + local.num_extra_cycles;
153
154 let next_clk =
155 AB::Expr::from_canonical_u32(1u32 << 16) * next.clk_8bit_limb + next.clk_16bit_limb;
156 builder.when_transition().when(next.is_real).assert_eq(expected_next_clk, next_clk);
157
158 // Range check that the clk is within 24 bits using it's limb values.
159 // SAFETY: `local.is_real` is checked to be boolean in `eval_is_real`.
160 builder.eval_range_check_24bits(
161 clk,
162 local.clk_16bit_limb,
163 local.clk_8bit_limb,
164 local.is_real,
165 );
166 }
167
168 /// Constraints related to the pc for non jump, branch, and halt instructions.
169 ///
170 /// The function will verify that the pc increments by 4 for all instructions except branch,
171 /// jump and halt instructions. Also, it ensures that the pc is carried down to the last row
172 /// for non-real rows.
173 pub(crate) fn eval_pc<AB: SP1AirBuilder>(
174 &self,
175 builder: &mut AB,
176 local: &CpuCols<AB::Var>,
177 next: &CpuCols<AB::Var>,
178 public_values: &PublicValues<Word<AB::PublicVar>, AB::PublicVar>,
179 ) {
180 // Verify the public value's start pc.
181 builder.when_first_row().assert_eq(public_values.start_pc, local.pc);
182
183 // Verify that the next row's `pc` is the current row's `next_pc`.
184 builder.when_transition().when(next.is_real).assert_eq(local.next_pc, next.pc);
185
186 // Verify the public value's next pc. We need to handle two cases:
187 // 1. The last real row is a transition row.
188 // 2. The last real row is the last row.
189
190 // If the last real row is a transition row, verify the public value's next pc.
191 builder
192 .when_transition()
193 .when(local.is_real - next.is_real)
194 .assert_eq(public_values.next_pc, local.next_pc);
195
196 // If the last real row is the last row, verify the public value's next pc.
197 builder.when_last_row().when(local.is_real).assert_eq(public_values.next_pc, local.next_pc);
198 }
199
200 /// Constraints related to the is_real column.
201 ///
202 /// This method checks that the is_real column is a boolean. It also checks that the first row
203 /// is 1 and once its 0, it never changes value.
204 pub(crate) fn eval_is_real<AB: SP1AirBuilder>(
205 &self,
206 builder: &mut AB,
207 local: &CpuCols<AB::Var>,
208 next: &CpuCols<AB::Var>,
209 ) {
210 // Check the is_real flag. It should be 1 for the first row. Once its 0, it should never
211 // change value.
212 builder.assert_bool(local.is_real);
213 builder.when_first_row().assert_one(local.is_real);
214 builder.when_transition().when_not(local.is_real).assert_zero(next.is_real);
215
216 // If we're halting and it's a transition, then the next.is_real should be 0.
217 builder.when_transition().when(local.is_halt).assert_zero(next.is_real);
218 }
219}
220
221impl<F> BaseAir<F> for CpuChip {
222 fn width(&self) -> usize {
223 NUM_CPU_COLS
224 }
225}